Merge "BatteryStatsService - call PermissionEnforcer helper on methods annotated with @EnforcePermission"
diff --git a/Android.bp b/Android.bp
index 0315c12..0a14565 100644
--- a/Android.bp
+++ b/Android.bp
@@ -214,6 +214,7 @@
         "android.hardware.radio-V1.5-java",
         "android.hardware.radio-V1.6-java",
         "android.hardware.radio.data-V1-java",
+        "android.hardware.radio.ims-V1-java",
         "android.hardware.radio.messaging-V1-java",
         "android.hardware.radio.modem-V1-java",
         "android.hardware.radio.network-V2-java",
diff --git a/apct-tests/perftests/core/Android.bp b/apct-tests/perftests/core/Android.bp
index ab20fdb..98e4f45 100644
--- a/apct-tests/perftests/core/Android.bp
+++ b/apct-tests/perftests/core/Android.bp
@@ -44,6 +44,8 @@
         "apct-perftests-utils",
         "collector-device-lib",
         "compatibility-device-util-axt",
+        "junit",
+        "junit-params",
         "core-tests-support",
         "guava",
     ],
diff --git a/apct-tests/perftests/core/src/android/libcore/DeepArrayOpsPerfTest.java b/apct-tests/perftests/core/src/android/libcore/DeepArrayOpsPerfTest.java
index 3f4f6af..3ebaa4c 100644
--- a/apct-tests/perftests/core/src/android/libcore/DeepArrayOpsPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/DeepArrayOpsPerfTest.java
@@ -20,18 +20,19 @@
 import android.perftests.utils.PerfStatusReporter;
 import android.test.suitebuilder.annotation.LargeTest;
 
-import org.junit.Before;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
 
 import java.lang.reflect.Array;
 import java.lang.reflect.Constructor;
 import java.util.Arrays;
 import java.util.Collection;
 
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
 @LargeTest
 public class DeepArrayOpsPerfTest {
     @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
@@ -39,19 +40,14 @@
     private Object[] mArray;
     private Object[] mArray2;
 
-    @Parameterized.Parameter(0)
-    public int mArrayLength;
-
-    @Parameterized.Parameters(name = "mArrayLength({0})")
-    public static Collection<Object[]> data() {
+    public static Collection<Object[]> getData() {
         return Arrays.asList(new Object[][] {{1}, {4}, {16}, {32}, {2048}});
     }
 
-    @Before
-    public void setUp() throws Exception {
-        mArray = new Object[mArrayLength * 14];
-        mArray2 = new Object[mArrayLength * 14];
-        for (int i = 0; i < mArrayLength; i += 14) {
+    public void setUp(int arrayLength) throws Exception {
+        mArray = new Object[arrayLength * 14];
+        mArray2 = new Object[arrayLength * 14];
+        for (int i = 0; i < arrayLength; i += 14) {
             mArray[i] = new IntWrapper(i);
             mArray2[i] = new IntWrapper(i);
 
@@ -99,7 +95,9 @@
     }
 
     @Test
-    public void deepHashCode() {
+    @Parameters(method = "getData")
+    public void deepHashCode(int arrayLength) throws Exception {
+        setUp(arrayLength);
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
             Arrays.deepHashCode(mArray);
@@ -107,7 +105,9 @@
     }
 
     @Test
-    public void deepEquals() {
+    @Parameters(method = "getData")
+    public void deepEquals(int arrayLength) throws Exception {
+        setUp(arrayLength);
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
             Arrays.deepEquals(mArray, mArray2);
diff --git a/apct-tests/perftests/core/src/android/libcore/SystemArrayCopyPerfTest.java b/apct-tests/perftests/core/src/android/libcore/SystemArrayCopyPerfTest.java
index 5aacfc2..20f1309 100644
--- a/apct-tests/perftests/core/src/android/libcore/SystemArrayCopyPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/SystemArrayCopyPerfTest.java
@@ -20,22 +20,22 @@
 import android.perftests.utils.PerfStatusReporter;
 import android.test.suitebuilder.annotation.LargeTest;
 
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
 
 import java.util.Arrays;
 import java.util.Collection;
 
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
 @LargeTest
 public class SystemArrayCopyPerfTest {
     @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
 
-    @Parameters(name = "arrayLength={0}")
-    public static Collection<Object[]> data() {
+    public static Collection<Object[]> getData() {
         return Arrays.asList(
                 new Object[][] {
                     {2}, {4}, {8}, {16}, {32}, {64}, {128}, {256}, {512}, {1024}, {2048}, {4096},
@@ -43,12 +43,10 @@
                 });
     }
 
-    @Parameterized.Parameter(0)
-    public int arrayLength;
-
     // Provides benchmarking for different types of arrays using the arraycopy function.
     @Test
-    public void timeSystemCharArrayCopy() {
+    @Parameters(method = "getData")
+    public void timeSystemCharArrayCopy(int arrayLength) {
         final int len = arrayLength;
         char[] src = new char[len];
         char[] dst = new char[len];
@@ -59,7 +57,8 @@
     }
 
     @Test
-    public void timeSystemByteArrayCopy() {
+    @Parameters(method = "getData")
+    public void timeSystemByteArrayCopy(int arrayLength) {
         final int len = arrayLength;
         byte[] src = new byte[len];
         byte[] dst = new byte[len];
@@ -70,7 +69,8 @@
     }
 
     @Test
-    public void timeSystemShortArrayCopy() {
+    @Parameters(method = "getData")
+    public void timeSystemShortArrayCopy(int arrayLength) {
         final int len = arrayLength;
         short[] src = new short[len];
         short[] dst = new short[len];
@@ -81,7 +81,8 @@
     }
 
     @Test
-    public void timeSystemIntArrayCopy() {
+    @Parameters(method = "getData")
+    public void timeSystemIntArrayCopy(int arrayLength) {
         final int len = arrayLength;
         int[] src = new int[len];
         int[] dst = new int[len];
@@ -92,7 +93,8 @@
     }
 
     @Test
-    public void timeSystemLongArrayCopy() {
+    @Parameters(method = "getData")
+    public void timeSystemLongArrayCopy(int arrayLength) {
         final int len = arrayLength;
         long[] src = new long[len];
         long[] dst = new long[len];
@@ -103,7 +105,8 @@
     }
 
     @Test
-    public void timeSystemFloatArrayCopy() {
+    @Parameters(method = "getData")
+    public void timeSystemFloatArrayCopy(int arrayLength) {
         final int len = arrayLength;
         float[] src = new float[len];
         float[] dst = new float[len];
@@ -114,7 +117,8 @@
     }
 
     @Test
-    public void timeSystemDoubleArrayCopy() {
+    @Parameters(method = "getData")
+    public void timeSystemDoubleArrayCopy(int arrayLength) {
         final int len = arrayLength;
         double[] src = new double[len];
         double[] dst = new double[len];
@@ -125,7 +129,8 @@
     }
 
     @Test
-    public void timeSystemBooleanArrayCopy() {
+    @Parameters(method = "getData")
+    public void timeSystemBooleanArrayCopy(int arrayLength) {
         final int len = arrayLength;
         boolean[] src = new boolean[len];
         boolean[] dst = new boolean[len];
diff --git a/apct-tests/perftests/core/src/android/libcore/XmlSerializePerfTest.java b/apct-tests/perftests/core/src/android/libcore/XmlSerializePerfTest.java
index eec0734..b1b594d 100644
--- a/apct-tests/perftests/core/src/android/libcore/XmlSerializePerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/XmlSerializePerfTest.java
@@ -20,42 +20,32 @@
 import android.perftests.utils.PerfStatusReporter;
 import android.test.suitebuilder.annotation.LargeTest;
 
-import org.junit.Before;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
 import org.xmlpull.v1.XmlSerializer;
 
 import java.io.CharArrayWriter;
 import java.lang.reflect.Constructor;
-import java.util.Arrays;
-import java.util.Collection;
 import java.util.Random;
 
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
 @LargeTest
 public class XmlSerializePerfTest {
     @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
 
-    @Parameters(name = "mDatasetAsString({0}), mSeed({1})")
-    public static Collection<Object[]> data() {
-        return Arrays.asList(
-                new Object[][] {
-                    {"0.99 0.7 0.7 0.7 0.7 0.7", 854328},
-                    {"0.999 0.3 0.3 0.95 0.9 0.9", 854328},
-                    {"0.99 0.7 0.7 0.7 0.7 0.7", 312547},
-                    {"0.999 0.3 0.3 0.95 0.9 0.9", 312547}
-                });
+    private Object[] getParams() {
+        return new Object[][] {
+            new Object[] {"0.99 0.7 0.7 0.7 0.7 0.7", 854328},
+            new Object[] {"0.999 0.3 0.3 0.95 0.9 0.9", 854328},
+            new Object[] {"0.99 0.7 0.7 0.7 0.7 0.7", 312547},
+            new Object[] {"0.999 0.3 0.3 0.95 0.9 0.9", 312547}
+        };
     }
 
-    @Parameterized.Parameter(0)
-    public String mDatasetAsString;
-
-    @Parameterized.Parameter(1)
-    public int mSeed;
-
     double[] mDataset;
     private Constructor<? extends XmlSerializer> mKxmlConstructor;
     private Constructor<? extends XmlSerializer> mFastConstructor;
@@ -100,8 +90,7 @@
     }
 
     @SuppressWarnings("unchecked")
-    @Before
-    public void setUp() throws Exception {
+    public void setUp(String datasetAsString) throws Exception {
         mKxmlConstructor =
                 (Constructor)
                         Class.forName("com.android.org.kxml2.io.KXmlSerializer").getConstructor();
@@ -109,28 +98,32 @@
                 (Constructor)
                         Class.forName("com.android.internal.util.FastXmlSerializer")
                                 .getConstructor();
-        String[] splitStrings = mDatasetAsString.split(" ");
+        String[] splitStrings = datasetAsString.split(" ");
         mDataset = new double[splitStrings.length];
         for (int i = 0; i < splitStrings.length; i++) {
             mDataset[i] = Double.parseDouble(splitStrings[i]);
         }
     }
 
-    private void internalTimeSerializer(Constructor<? extends XmlSerializer> ctor)
+    private void internalTimeSerializer(Constructor<? extends XmlSerializer> ctor, int seed)
             throws Exception {
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            serializeRandomXml(ctor, mSeed);
+            serializeRandomXml(ctor, seed);
         }
     }
 
     @Test
-    public void timeKxml() throws Exception {
-        internalTimeSerializer(mKxmlConstructor);
+    @Parameters(method = "getParams")
+    public void timeKxml(String datasetAsString, int seed) throws Exception {
+        setUp(datasetAsString);
+        internalTimeSerializer(mKxmlConstructor, seed);
     }
 
     @Test
-    public void timeFast() throws Exception {
-        internalTimeSerializer(mFastConstructor);
+    @Parameters(method = "getParams")
+    public void timeFast(String datasetAsString, int seed) throws Exception {
+        setUp(datasetAsString);
+        internalTimeSerializer(mFastConstructor, seed);
     }
 }
diff --git a/apct-tests/perftests/core/src/android/libcore/ZipFilePerfTest.java b/apct-tests/perftests/core/src/android/libcore/ZipFilePerfTest.java
index 31c92ba..3a45d40 100644
--- a/apct-tests/perftests/core/src/android/libcore/ZipFilePerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/ZipFilePerfTest.java
@@ -20,12 +20,12 @@
 import android.perftests.utils.PerfStatusReporter;
 import android.test.suitebuilder.annotation.LargeTest;
 
-import org.junit.Before;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
 
 import java.io.File;
 import java.io.FileOutputStream;
@@ -38,23 +38,18 @@
 import java.util.zip.ZipFile;
 import java.util.zip.ZipOutputStream;
 
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
 @LargeTest
 public class ZipFilePerfTest {
     @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
 
     private File mFile;
 
-    @Parameters(name = "numEntries={0}")
-    public static Collection<Object[]> data() {
+    public static Collection<Object[]> getData() {
         return Arrays.asList(new Object[][] {{128}, {1024}, {8192}});
     }
 
-    @Parameterized.Parameter(0)
-    public int numEntries;
-
-    @Before
-    public void setUp() throws Exception {
+    public void setUp(int numEntries) throws Exception {
         mFile = File.createTempFile(getClass().getName(), ".zip");
         mFile.deleteOnExit();
         writeEntries(new ZipOutputStream(new FileOutputStream(mFile)), numEntries, 0);
@@ -66,7 +61,9 @@
     }
 
     @Test
-    public void timeZipFileOpen() throws Exception {
+    @Parameters(method = "getData")
+    public void timeZipFileOpen(int numEntries) throws Exception {
+        setUp(numEntries);
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
             ZipFile zf = new ZipFile(mFile);
diff --git a/apct-tests/perftests/core/src/android/libcore/ZipFileReadPerfTest.java b/apct-tests/perftests/core/src/android/libcore/ZipFileReadPerfTest.java
index faa9628..2e89518 100644
--- a/apct-tests/perftests/core/src/android/libcore/ZipFileReadPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/ZipFileReadPerfTest.java
@@ -20,12 +20,13 @@
 import android.perftests.utils.PerfStatusReporter;
 import android.test.suitebuilder.annotation.LargeTest;
 
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
 
 import java.io.File;
 import java.io.FileOutputStream;
@@ -39,21 +40,17 @@
 import java.util.zip.ZipFile;
 import java.util.zip.ZipOutputStream;
 
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
 @LargeTest
 public class ZipFileReadPerfTest {
     @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
 
-    @Parameters(name = "readBufferSize={0}")
-    public static Collection<Object[]> data() {
+    public static Collection<Object[]> getData() {
         return Arrays.asList(new Object[][] {{1024}, {16384}, {65536}});
     }
 
     private File mFile;
 
-    @Parameterized.Parameter(0)
-    public int readBufferSize;
-
     @Before
     public void setUp() throws Exception {
         mFile = File.createTempFile(getClass().getName(), ".zip");
@@ -90,7 +87,8 @@
     }
 
     @Test
-    public void timeZipFileRead() throws Exception {
+    @Parameters(method = "getData")
+    public void timeZipFileRead(int readBufferSize) throws Exception {
         byte[] readBuffer = new byte[readBufferSize];
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/BitSetPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/BitSetPerfTest.java
index db5462c..2c0473e 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/BitSetPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/BitSetPerfTest.java
@@ -20,96 +20,99 @@
 import android.perftests.utils.PerfStatusReporter;
 import android.test.suitebuilder.annotation.LargeTest;
 
-import org.junit.Before;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
 
 import java.util.Arrays;
 import java.util.BitSet;
 import java.util.Collection;
 
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
 @LargeTest
 public class BitSetPerfTest {
     @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
 
-    @Parameters(name = "mSize={0}")
-    public static Collection<Object[]> data() {
+    public static Collection<Object[]> getData() {
         return Arrays.asList(new Object[][] {{1000}, {10000}});
     }
 
-    @Parameterized.Parameter(0)
-    public int mSize;
-
-    private BitSet mBitSet;
-
-    @Before
-    public void setUp() throws Exception {
-        mBitSet = new BitSet(mSize);
-    }
-
     @Test
-    public void timeIsEmptyTrue() {
+    @Parameters(method = "getData")
+    public void timeIsEmptyTrue(int size) {
+        BitSet bitSet = new BitSet(size);
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            if (!mBitSet.isEmpty()) throw new RuntimeException();
+            if (!bitSet.isEmpty()) throw new RuntimeException();
         }
     }
 
     @Test
-    public void timeIsEmptyFalse() {
-        mBitSet.set(mBitSet.size() - 1);
+    @Parameters(method = "getData")
+    public void timeIsEmptyFalse(int size) {
+        BitSet bitSet = new BitSet(size);
+        bitSet.set(bitSet.size() - 1);
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            if (mBitSet.isEmpty()) throw new RuntimeException();
+            if (bitSet.isEmpty()) throw new RuntimeException();
         }
     }
 
     @Test
-    public void timeGet() {
+    @Parameters(method = "getData")
+    public void timeGet(int size) {
+        BitSet bitSet = new BitSet(size);
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         int i = 1;
         while (state.keepRunning()) {
-            mBitSet.get(++i % mSize);
+            bitSet.get(++i % size);
         }
     }
 
     @Test
-    public void timeClear() {
+    @Parameters(method = "getData")
+    public void timeClear(int size) {
+        BitSet bitSet = new BitSet(size);
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         int i = 1;
         while (state.keepRunning()) {
-            mBitSet.clear(++i % mSize);
+            bitSet.clear(++i % size);
         }
     }
 
     @Test
-    public void timeSet() {
+    @Parameters(method = "getData")
+    public void timeSet(int size) {
+        BitSet bitSet = new BitSet(size);
         int i = 1;
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            mBitSet.set(++i % mSize);
+            bitSet.set(++i % size);
         }
     }
 
     @Test
-    public void timeSetOn() {
+    @Parameters(method = "getData")
+    public void timeSetOn(int size) {
+        BitSet bitSet = new BitSet(size);
         int i = 1;
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            mBitSet.set(++i % mSize, true);
+            bitSet.set(++i % size, true);
         }
     }
 
     @Test
-    public void timeSetOff() {
+    @Parameters(method = "getData")
+    public void timeSetOff(int size) {
+        BitSet bitSet = new BitSet(size);
         int i = 1;
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            mBitSet.set(++i % mSize, false);
+            bitSet.set(++i % size, false);
         }
     }
 }
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/BreakIteratorPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/BreakIteratorPerfTest.java
index 3952c12..6a2ce58 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/BreakIteratorPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/BreakIteratorPerfTest.java
@@ -20,18 +20,19 @@
 import android.perftests.utils.PerfStatusReporter;
 import android.test.suitebuilder.annotation.LargeTest;
 
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
 
 import java.text.BreakIterator;
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Locale;
 
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
 @LargeTest
 public final class BreakIteratorPerfTest {
     @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
@@ -41,36 +42,37 @@
                 Locale.US,
                 "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi mollis consequat"
                     + " nisl non pharetra. Praesent pretium vehicula odio sed ultrices. Aenean a"
-                    + " felis libero. Vivamus sed commodo nibh. Pellentesque turpis lectus, euismod"
-                    + " vel ante nec, cursus posuere orci. Suspendisse velit neque, fermentum"
-                    + " luctus ultrices in, ultrices vitae arcu. Duis tincidunt cursus lorem. Nam"
-                    + " ultricies accumsan quam vitae imperdiet. Pellentesque habitant morbi"
-                    + " tristique senectus et netus et malesuada fames ac turpis egestas. Quisque"
-                    + " aliquet pretium nisi, eget laoreet enim molestie sit amet. Class aptent"
-                    + " taciti sociosqu ad litora torquent per conubia nostra, per inceptos"
+                    + " felis libero. Vivamus sed commodo nibh. Pellentesque turpis lectus,"
+                    + " euismod vel ante nec, cursus posuere orci. Suspendisse velit neque,"
+                    + " fermentum luctus ultrices in, ultrices vitae arcu. Duis tincidunt cursus"
+                    + " lorem. Nam ultricies accumsan quam vitae imperdiet. Pellentesque habitant"
+                    + " morbi tristique senectus et netus et malesuada fames ac turpis egestas."
+                    + " Quisque aliquet pretium nisi, eget laoreet enim molestie sit amet. Class"
+                    + " aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos"
                     + " himenaeos.\n"
                     + "Nam dapibus aliquam lacus ac suscipit. Proin in nibh sit amet purus congue"
                     + " laoreet eget quis nisl. Morbi gravida dignissim justo, a venenatis ante"
-                    + " pulvinar at. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin"
-                    + " ultrices vestibulum dui, vel aliquam lacus aliquam quis. Duis fringilla"
-                    + " sapien ac lacus egestas, vel adipiscing elit euismod. Donec non tellus"
-                    + " odio. Donec gravida eu massa ac feugiat. Aliquam erat volutpat. Praesent id"
-                    + " adipiscing metus, nec laoreet enim. Aliquam vitae posuere turpis. Mauris ac"
-                    + " pharetra sem. In at placerat tortor. Vivamus ac vehicula neque. Cras"
-                    + " volutpat ullamcorper massa et varius. Praesent sagittis neque vitae nulla"
-                    + " euismod pharetra.\n"
+                    + " pulvinar at. Lorem ipsum dolor sit amet, consectetur adipiscing elit."
+                    + " Proin ultrices vestibulum dui, vel aliquam lacus aliquam quis. Duis"
+                    + " fringilla sapien ac lacus egestas, vel adipiscing elit euismod. Donec non"
+                    + " tellus odio. Donec gravida eu massa ac feugiat. Aliquam erat volutpat."
+                    + " Praesent id adipiscing metus, nec laoreet enim. Aliquam vitae posuere"
+                    + " turpis. Mauris ac pharetra sem. In at placerat tortor. Vivamus ac vehicula"
+                    + " neque. Cras volutpat ullamcorper massa et varius. Praesent sagittis neque"
+                    + " vitae nulla euismod pharetra.\n"
                     + "Sed placerat sapien non molestie sollicitudin. Nullam sit amet dictum quam."
                     + " Etiam tincidunt tortor vel pretium vehicula. Praesent fringilla ipsum vel"
                     + " velit luctus dignissim. Nulla massa ligula, mattis in enim et, mattis"
                     + " lacinia odio. Suspendisse tristique urna a orci commodo tempor. Duis"
                     + " lacinia egestas arcu a sollicitudin.\n"
                     + "In ac feugiat lacus. Nunc fermentum eu est at tristique. Pellentesque quis"
-                    + " ligula et orci placerat lacinia. Maecenas quis mauris diam. Etiam mi ipsum,"
-                    + " tempus in purus quis, euismod faucibus orci. Nulla facilisi. Praesent sit"
-                    + " amet sapien vel elit porta adipiscing. Phasellus sit amet volutpat diam.\n"
-                    + "Proin bibendum elit non lacus pharetra, quis eleifend tellus placerat. Nulla"
-                    + " facilisi. Maecenas ante diam, pellentesque mattis mattis in, porta ut"
-                    + " lorem. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices"
+                    + " ligula et orci placerat lacinia. Maecenas quis mauris diam. Etiam mi"
+                    + " ipsum, tempus in purus quis, euismod faucibus orci. Nulla facilisi."
+                    + " Praesent sit amet sapien vel elit porta adipiscing. Phasellus sit amet"
+                    + " volutpat diam.\n"
+                    + "Proin bibendum elit non lacus pharetra, quis eleifend tellus placerat."
+                    + " Nulla facilisi. Maecenas ante diam, pellentesque mattis mattis in, porta"
+                    + " ut lorem. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices"
                     + " posuere cubilia Curae; Nunc interdum tristique metus, in scelerisque odio"
                     + " fermentum eget. Cras nec venenatis lacus. Aenean euismod eget metus quis"
                     + " molestie. Cras tincidunt dolor ut massa ornare, in elementum lacus auctor."
@@ -80,29 +82,29 @@
         LONGPARA(
                 Locale.US,
                 "During dinner, Mr. Bennet scarcely spoke at all; but when the servants were"
-                    + " withdrawn, he thought it time to have some conversation with his guest, and"
-                    + " therefore started a subject in which he expected him to shine, by observing"
-                    + " that he seemed very fortunate in his patroness. Lady Catherine de Bourgh's"
-                    + " attention to his wishes, and consideration for his comfort, appeared very"
-                    + " remarkable. Mr. Bennet could not have chosen better. Mr. Collins was"
-                    + " eloquent in her praise. The subject elevated him to more than usual"
-                    + " solemnity of manner, and with a most important aspect he protested that"
-                    + " \"he had never in his life witnessed such behaviour in a person of"
-                    + " rank--such affability and condescension, as he had himself experienced from"
-                    + " Lady Catherine. She had been graciously pleased to approve of both of the"
-                    + " discourses which he had already had the honour of preaching before her. She"
-                    + " had also asked him twice to dine at Rosings, and had sent for him only the"
-                    + " Saturday before, to make up her pool of quadrille in the evening. Lady"
-                    + " Catherine was reckoned proud by many people he knew, but _he_ had never"
-                    + " seen anything but affability in her. She had always spoken to him as she"
-                    + " would to any other gentleman; she made not the smallest objection to his"
-                    + " joining in the society of the neighbourhood nor to his leaving the parish"
-                    + " occasionally for a week or two, to visit his relations. She had even"
-                    + " condescended to advise him to marry as soon as he could, provided he chose"
-                    + " with discretion; and had once paid him a visit in his humble parsonage,"
-                    + " where she had perfectly approved all the alterations he had been making,"
-                    + " and had even vouchsafed to suggest some herself--some shelves in the closet"
-                    + " up stairs.\""),
+                    + " withdrawn, he thought it time to have some conversation with his guest,"
+                    + " and therefore started a subject in which he expected him to shine, by"
+                    + " observing that he seemed very fortunate in his patroness. Lady Catherine"
+                    + " de Bourgh's attention to his wishes, and consideration for his comfort,"
+                    + " appeared very remarkable. Mr. Bennet could not have chosen better. Mr."
+                    + " Collins was eloquent in her praise. The subject elevated him to more than"
+                    + " usual solemnity of manner, and with a most important aspect he protested"
+                    + " that \"he had never in his life witnessed such behaviour in a person of"
+                    + " rank--such affability and condescension, as he had himself experienced"
+                    + " from Lady Catherine. She had been graciously pleased to approve of both of"
+                    + " the discourses which he had already had the honour of preaching before"
+                    + " her. She had also asked him twice to dine at Rosings, and had sent for him"
+                    + " only the Saturday before, to make up her pool of quadrille in the evening."
+                    + " Lady Catherine was reckoned proud by many people he knew, but _he_ had"
+                    + " never seen anything but affability in her. She had always spoken to him as"
+                    + " she would to any other gentleman; she made not the smallest objection to"
+                    + " his joining in the society of the neighbourhood nor to his leaving the"
+                    + " parish occasionally for a week or two, to visit his relations. She had"
+                    + " even condescended to advise him to marry as soon as he could, provided he"
+                    + " chose with discretion; and had once paid him a visit in his humble"
+                    + " parsonage, where she had perfectly approved all the alterations he had"
+                    + " been making, and had even vouchsafed to suggest some herself--some shelves"
+                    + " in the closet up stairs.\""),
         GERMAN(
                 Locale.GERMANY,
                 "Aber dieser Freiheit setzte endlich der Winter ein Ziel. Draußen auf den Feldern"
@@ -119,15 +121,14 @@
                     + " เดิมทีเป็นการผสมผสานกันระหว่างสำเนียงอยุธยาและชาวไทยเชื้อสายจีนรุ่นหลังที่"
                     + "พูดไทยแทนกลุ่มภาษาจีน"
                     + " ลักษณะเด่นคือมีการออกเสียงที่ชัดเจนและแข็งกระด้างซึ่งได้รับอิทธิพลจากภาษาแต"
-                    + "้จิ๋ว"
-                    + " การออกเสียงพยัญชนะ สระ การผันวรรณยุกต์ที่ในภาษาไทยมาตรฐาน"
+                    + "้จิ๋ว การออกเสียงพยัญชนะ สระ การผันวรรณยุกต์ที่ในภาษาไทยมาตรฐาน"
                     + " มาจากสำเนียงถิ่นนี้ในขณะที่ภาษาไทยสำเนียงอื่นล้วนเหน่อทั้งสิ้น"
                     + " คำศัพท์ที่ใช้ในสำเนียงกรุงเทพจำนวนมากได้รับมาจากกลุ่มภาษาจีนเช่นคำว่า โป๊,"
                     + " เฮ็ง, อาหมวย, อาซิ่ม ซึ่งมาจากภาษาแต้จิ๋ว และจากภาษาจีนเช่น ถู(涂), ชิ่ว(去"
                     + " อ่านว่า\"ชู่\") และคำว่า ทาย(猜 อ่านว่า \"ชาย\") เป็นต้น"
                     + " เนื่องจากสำเนียงกรุงเทพได้รับอิทธิพลมาจากภาษาจีนดังนั้นตัวอักษร \"ร\""
-                    + " มักออกเสียงเหมารวมเป็น \"ล\" หรือคำควบกล่ำบางคำถูกละทิ้งไปด้วยเช่น รู้ เป็น"
-                    + " ลู้, เรื่อง เป็น เลื่อง หรือ ประเทศ เป็น ปะเทศ"
+                    + " มักออกเสียงเหมารวมเป็น \"ล\" หรือคำควบกล่ำบางคำถูกละทิ้งไปด้วยเช่น รู้"
+                    + " เป็น ลู้, เรื่อง เป็น เลื่อง หรือ ประเทศ เป็น ปะเทศ"
                     + " เป็นต้นสร้างความลำบากให้แก่ต่างชาติที่ต้องการเรียนภาษาไทย"
                     + " แต่อย่างไรก็ตามผู้ที่พูดสำเนียงถิ่นนี้ก็สามารถออกอักขระภาษาไทยตามมาตรฐานได"
                     + "้อย่างถูกต้องเพียงแต่มักเผลอไม่ค่อยออกเสียง"),
@@ -151,8 +152,7 @@
         }
     }
 
-    @Parameters(name = "mText={0}")
-    public static Collection<Object[]> data() {
+    public static Collection<Object[]> getData() {
         return Arrays.asList(
                 new Object[][] {
                     {Text.ACCENT}, {Text.BIDI}, {Text.EMOJI}, {Text.EMPTY}, {Text.GERMAN},
@@ -161,15 +161,13 @@
                 });
     }
 
-    @Parameterized.Parameter(0)
-    public Text mText;
-
     @Test
-    public void timeBreakIterator() {
+    @Parameters(method = "getData")
+    public void timeBreakIterator(Text text) {
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            BreakIterator it = BreakIterator.getLineInstance(mText.mLocale);
-            it.setText(mText.mText);
+            BreakIterator it = BreakIterator.getLineInstance(text.mLocale);
+            it.setText(text.mText);
 
             while (it.next() != BreakIterator.DONE) {
                 // Keep iterating
@@ -178,12 +176,13 @@
     }
 
     @Test
-    public void timeIcuBreakIterator() {
+    @Parameters(method = "getData")
+    public void timeIcuBreakIterator(Text text) {
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
             android.icu.text.BreakIterator it =
-                    android.icu.text.BreakIterator.getLineInstance(mText.mLocale);
-            it.setText(mText.mText);
+                    android.icu.text.BreakIterator.getLineInstance(text.mLocale);
+            it.setText(text.mText);
 
             while (it.next() != android.icu.text.BreakIterator.DONE) {
                 // Keep iterating
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/BulkPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/BulkPerfTest.java
index 855bb9a..b7b7e83 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/BulkPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/BulkPerfTest.java
@@ -20,11 +20,12 @@
 import android.perftests.utils.PerfStatusReporter;
 import android.test.suitebuilder.annotation.LargeTest;
 
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
 
 import java.io.File;
 import java.io.IOException;
@@ -34,13 +35,12 @@
 import java.util.Arrays;
 import java.util.Collection;
 
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
 @LargeTest
 public class BulkPerfTest {
     @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
 
-    @Parameters(name = "mAlign({0}), mSBuf({1}), mDBuf({2}), mSize({3})")
-    public static Collection<Object[]> data() {
+    public static Collection<Object[]> getData() {
         return Arrays.asList(
                 new Object[][] {
                     {true, MyBufferType.DIRECT, MyBufferType.DIRECT, 4096},
@@ -82,24 +82,12 @@
                 });
     }
 
-    @Parameterized.Parameter(0)
-    public boolean mAlign;
-
     enum MyBufferType {
         DIRECT,
         HEAP,
         MAPPED
     }
 
-    @Parameterized.Parameter(1)
-    public MyBufferType mSBuf;
-
-    @Parameterized.Parameter(2)
-    public MyBufferType mDBuf;
-
-    @Parameterized.Parameter(3)
-    public int mSize;
-
     public static ByteBuffer newBuffer(boolean aligned, MyBufferType bufferType, int bsize)
             throws IOException {
         int size = aligned ? bsize : bsize + 8 + 1;
@@ -126,13 +114,15 @@
     }
 
     @Test
-    public void timePut() throws Exception {
-        ByteBuffer src = BulkPerfTest.newBuffer(mAlign, mSBuf, mSize);
-        ByteBuffer data = BulkPerfTest.newBuffer(mAlign, mDBuf, mSize);
+    @Parameters(method = "getData")
+    public void timePut(boolean align, MyBufferType sBuf, MyBufferType dBuf, int size)
+            throws Exception {
+        ByteBuffer src = BulkPerfTest.newBuffer(align, sBuf, size);
+        ByteBuffer data = BulkPerfTest.newBuffer(align, dBuf, size);
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            src.position(mAlign ? 0 : 1);
-            data.position(mAlign ? 0 : 1);
+            src.position(align ? 0 : 1);
+            data.position(align ? 0 : 1);
             src.put(data);
         }
     }
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/ByteBufferPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/ByteBufferPerfTest.java
index 4bd7c4e..9ac36d0 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/ByteBufferPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/ByteBufferPerfTest.java
@@ -20,11 +20,12 @@
 import android.perftests.utils.PerfStatusReporter;
 import android.test.suitebuilder.annotation.LargeTest;
 
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
 
 import java.io.File;
 import java.io.IOException;
@@ -41,7 +42,7 @@
 import java.util.Arrays;
 import java.util.Collection;
 
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
 @LargeTest
 public class ByteBufferPerfTest {
     @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
@@ -49,15 +50,14 @@
     public enum MyByteOrder {
         BIG(ByteOrder.BIG_ENDIAN),
         LITTLE(ByteOrder.LITTLE_ENDIAN);
-        final ByteOrder mByteOrder;
+        final ByteOrder byteOrder;
 
-        MyByteOrder(ByteOrder mByteOrder) {
-            this.mByteOrder = mByteOrder;
+        MyByteOrder(ByteOrder byteOrder) {
+            this.byteOrder = byteOrder;
         }
     }
 
-    @Parameters(name = "mByteOrder={0}, mAligned={1}, mBufferType={2}")
-    public static Collection<Object[]> data() {
+    public static Collection<Object[]> getData() {
         return Arrays.asList(
                 new Object[][] {
                     {MyByteOrder.BIG, true, MyBufferType.DIRECT},
@@ -75,21 +75,12 @@
                 });
     }
 
-    @Parameterized.Parameter(0)
-    public MyByteOrder mByteOrder;
-
-    @Parameterized.Parameter(1)
-    public boolean mAligned;
-
     enum MyBufferType {
         DIRECT,
         HEAP,
         MAPPED;
     }
 
-    @Parameterized.Parameter(2)
-    public MyBufferType mBufferType;
-
     public static ByteBuffer newBuffer(
             MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws IOException {
         int size = aligned ? 8192 : 8192 + 8 + 1;
@@ -115,7 +106,7 @@
                 result = fc.map(FileChannel.MapMode.READ_WRITE, 0, fc.size());
                 break;
         }
-        result.order(byteOrder.mByteOrder);
+        result.order(byteOrder.byteOrder);
         result.position(aligned ? 0 : 1);
         return result;
     }
@@ -125,11 +116,13 @@
     //
 
     @Test
-    public void timeByteBuffer_getByte() throws Exception {
-        ByteBuffer src = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
+    @Parameters(method = "getData")
+    public void timeByteBuffer_getByte(
+            MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+        ByteBuffer src = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            src.position(mAligned ? 0 : 1);
+            src.position(aligned ? 0 : 1);
             for (int i = 0; i < 1024; ++i) {
                 src.get();
             }
@@ -137,24 +130,28 @@
     }
 
     @Test
-    public void timeByteBuffer_getByteArray() throws Exception {
-        ByteBuffer src = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
+    @Parameters(method = "getData")
+    public void timeByteBuffer_getByteArray(
+            MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+        ByteBuffer src = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
         byte[] dst = new byte[1024];
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
             for (int i = 0; i < 1024; ++i) {
-                src.position(mAligned ? 0 : 1);
+                src.position(aligned ? 0 : 1);
                 src.get(dst);
             }
         }
     }
 
     @Test
-    public void timeByteBuffer_getByte_indexed() throws Exception {
-        ByteBuffer src = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
+    @Parameters(method = "getData")
+    public void timeByteBuffer_getByte_indexed(
+            MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+        ByteBuffer src = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            src.position(mAligned ? 0 : 1);
+            src.position(aligned ? 0 : 1);
             for (int i = 0; i < 1024; ++i) {
                 src.get(i);
             }
@@ -162,11 +159,13 @@
     }
 
     @Test
-    public void timeByteBuffer_getChar() throws Exception {
-        ByteBuffer src = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
+    @Parameters(method = "getData")
+    public void timeByteBuffer_getChar(
+            MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+        ByteBuffer src = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            src.position(mAligned ? 0 : 1);
+            src.position(aligned ? 0 : 1);
             for (int i = 0; i < 1024; ++i) {
                 src.getChar();
             }
@@ -174,9 +173,11 @@
     }
 
     @Test
-    public void timeCharBuffer_getCharArray() throws Exception {
+    @Parameters(method = "getData")
+    public void timeCharBuffer_getCharArray(
+            MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
         CharBuffer src =
-                ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType).asCharBuffer();
+                ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType).asCharBuffer();
         char[] dst = new char[1024];
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
@@ -188,11 +189,13 @@
     }
 
     @Test
-    public void timeByteBuffer_getChar_indexed() throws Exception {
-        ByteBuffer src = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
+    @Parameters(method = "getData")
+    public void timeByteBuffer_getChar_indexed(
+            MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+        ByteBuffer src = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            src.position(mAligned ? 0 : 1);
+            src.position(aligned ? 0 : 1);
             for (int i = 0; i < 1024; ++i) {
                 src.getChar(i * 2);
             }
@@ -200,11 +203,13 @@
     }
 
     @Test
-    public void timeByteBuffer_getDouble() throws Exception {
-        ByteBuffer src = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
+    @Parameters(method = "getData")
+    public void timeByteBuffer_getDouble(
+            MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+        ByteBuffer src = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            src.position(mAligned ? 0 : 1);
+            src.position(aligned ? 0 : 1);
             for (int i = 0; i < 1024; ++i) {
                 src.getDouble();
             }
@@ -212,9 +217,11 @@
     }
 
     @Test
-    public void timeDoubleBuffer_getDoubleArray() throws Exception {
+    @Parameters(method = "getData")
+    public void timeDoubleBuffer_getDoubleArray(
+            MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
         DoubleBuffer src =
-                ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType).asDoubleBuffer();
+                ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType).asDoubleBuffer();
         double[] dst = new double[1024];
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
@@ -226,11 +233,13 @@
     }
 
     @Test
-    public void timeByteBuffer_getFloat() throws Exception {
-        ByteBuffer src = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
+    @Parameters(method = "getData")
+    public void timeByteBuffer_getFloat(
+            MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+        ByteBuffer src = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            src.position(mAligned ? 0 : 1);
+            src.position(aligned ? 0 : 1);
             for (int i = 0; i < 1024; ++i) {
                 src.getFloat();
             }
@@ -238,9 +247,11 @@
     }
 
     @Test
-    public void timeFloatBuffer_getFloatArray() throws Exception {
+    @Parameters(method = "getData")
+    public void timeFloatBuffer_getFloatArray(
+            MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
         FloatBuffer src =
-                ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType).asFloatBuffer();
+                ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType).asFloatBuffer();
         float[] dst = new float[1024];
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
@@ -252,11 +263,13 @@
     }
 
     @Test
-    public void timeByteBuffer_getInt() throws Exception {
-        ByteBuffer src = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
+    @Parameters(method = "getData")
+    public void timeByteBuffer_getInt(
+            MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+        ByteBuffer src = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            src.position(mAligned ? 0 : 1);
+            src.position(aligned ? 0 : 1);
             for (int i = 0; i < 1024; ++i) {
                 src.getInt();
             }
@@ -264,9 +277,10 @@
     }
 
     @Test
-    public void timeIntBuffer_getIntArray() throws Exception {
-        IntBuffer src =
-                ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType).asIntBuffer();
+    @Parameters(method = "getData")
+    public void timeIntBuffer_getIntArray(
+            MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+        IntBuffer src = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType).asIntBuffer();
         int[] dst = new int[1024];
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
@@ -278,11 +292,13 @@
     }
 
     @Test
-    public void timeByteBuffer_getLong() throws Exception {
-        ByteBuffer src = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
+    @Parameters(method = "getData")
+    public void timeByteBuffer_getLong(
+            MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+        ByteBuffer src = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            src.position(mAligned ? 0 : 1);
+            src.position(aligned ? 0 : 1);
             for (int i = 0; i < 1024; ++i) {
                 src.getLong();
             }
@@ -290,9 +306,11 @@
     }
 
     @Test
-    public void timeLongBuffer_getLongArray() throws Exception {
+    @Parameters(method = "getData")
+    public void timeLongBuffer_getLongArray(
+            MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
         LongBuffer src =
-                ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType).asLongBuffer();
+                ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType).asLongBuffer();
         long[] dst = new long[1024];
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
@@ -304,11 +322,13 @@
     }
 
     @Test
-    public void timeByteBuffer_getShort() throws Exception {
-        ByteBuffer src = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
+    @Parameters(method = "getData")
+    public void timeByteBuffer_getShort(
+            MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+        ByteBuffer src = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            src.position(mAligned ? 0 : 1);
+            src.position(aligned ? 0 : 1);
             for (int i = 0; i < 1024; ++i) {
                 src.getShort();
             }
@@ -316,9 +336,11 @@
     }
 
     @Test
-    public void timeShortBuffer_getShortArray() throws Exception {
+    @Parameters(method = "getData")
+    public void timeShortBuffer_getShortArray(
+            MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
         ShortBuffer src =
-                ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType).asShortBuffer();
+                ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType).asShortBuffer();
         short[] dst = new short[1024];
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
@@ -334,8 +356,10 @@
     //
 
     @Test
-    public void timeByteBuffer_putByte() throws Exception {
-        ByteBuffer src = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
+    @Parameters(method = "getData")
+    public void timeByteBuffer_putByte(
+            MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+        ByteBuffer src = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
             src.position(0);
@@ -346,24 +370,28 @@
     }
 
     @Test
-    public void timeByteBuffer_putByteArray() throws Exception {
-        ByteBuffer dst = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
+    @Parameters(method = "getData")
+    public void timeByteBuffer_putByteArray(
+            MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+        ByteBuffer dst = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
         byte[] src = new byte[1024];
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
             for (int i = 0; i < 1024; ++i) {
-                dst.position(mAligned ? 0 : 1);
+                dst.position(aligned ? 0 : 1);
                 dst.put(src);
             }
         }
     }
 
     @Test
-    public void timeByteBuffer_putChar() throws Exception {
-        ByteBuffer src = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
+    @Parameters(method = "getData")
+    public void timeByteBuffer_putChar(
+            MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+        ByteBuffer src = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            src.position(mAligned ? 0 : 1);
+            src.position(aligned ? 0 : 1);
             for (int i = 0; i < 1024; ++i) {
                 src.putChar(' ');
             }
@@ -371,9 +399,11 @@
     }
 
     @Test
-    public void timeCharBuffer_putCharArray() throws Exception {
+    @Parameters(method = "getData")
+    public void timeCharBuffer_putCharArray(
+            MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
         CharBuffer dst =
-                ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType).asCharBuffer();
+                ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType).asCharBuffer();
         char[] src = new char[1024];
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
@@ -385,11 +415,13 @@
     }
 
     @Test
-    public void timeByteBuffer_putDouble() throws Exception {
-        ByteBuffer src = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
+    @Parameters(method = "getData")
+    public void timeByteBuffer_putDouble(
+            MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+        ByteBuffer src = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            src.position(mAligned ? 0 : 1);
+            src.position(aligned ? 0 : 1);
             for (int i = 0; i < 1024; ++i) {
                 src.putDouble(0.0);
             }
@@ -397,9 +429,11 @@
     }
 
     @Test
-    public void timeDoubleBuffer_putDoubleArray() throws Exception {
+    @Parameters(method = "getData")
+    public void timeDoubleBuffer_putDoubleArray(
+            MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
         DoubleBuffer dst =
-                ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType).asDoubleBuffer();
+                ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType).asDoubleBuffer();
         double[] src = new double[1024];
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
@@ -411,11 +445,13 @@
     }
 
     @Test
-    public void timeByteBuffer_putFloat() throws Exception {
-        ByteBuffer src = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
+    @Parameters(method = "getData")
+    public void timeByteBuffer_putFloat(
+            MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+        ByteBuffer src = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            src.position(mAligned ? 0 : 1);
+            src.position(aligned ? 0 : 1);
             for (int i = 0; i < 1024; ++i) {
                 src.putFloat(0.0f);
             }
@@ -423,9 +459,11 @@
     }
 
     @Test
-    public void timeFloatBuffer_putFloatArray() throws Exception {
+    @Parameters(method = "getData")
+    public void timeFloatBuffer_putFloatArray(
+            MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
         FloatBuffer dst =
-                ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType).asFloatBuffer();
+                ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType).asFloatBuffer();
         float[] src = new float[1024];
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
@@ -437,11 +475,13 @@
     }
 
     @Test
-    public void timeByteBuffer_putInt() throws Exception {
-        ByteBuffer src = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
+    @Parameters(method = "getData")
+    public void timeByteBuffer_putInt(
+            MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+        ByteBuffer src = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            src.position(mAligned ? 0 : 1);
+            src.position(aligned ? 0 : 1);
             for (int i = 0; i < 1024; ++i) {
                 src.putInt(0);
             }
@@ -449,9 +489,10 @@
     }
 
     @Test
-    public void timeIntBuffer_putIntArray() throws Exception {
-        IntBuffer dst =
-                ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType).asIntBuffer();
+    @Parameters(method = "getData")
+    public void timeIntBuffer_putIntArray(
+            MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+        IntBuffer dst = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType).asIntBuffer();
         int[] src = new int[1024];
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
@@ -463,11 +504,13 @@
     }
 
     @Test
-    public void timeByteBuffer_putLong() throws Exception {
-        ByteBuffer src = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
+    @Parameters(method = "getData")
+    public void timeByteBuffer_putLong(
+            MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+        ByteBuffer src = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            src.position(mAligned ? 0 : 1);
+            src.position(aligned ? 0 : 1);
             for (int i = 0; i < 1024; ++i) {
                 src.putLong(0L);
             }
@@ -475,9 +518,11 @@
     }
 
     @Test
-    public void timeLongBuffer_putLongArray() throws Exception {
+    @Parameters(method = "getData")
+    public void timeLongBuffer_putLongArray(
+            MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
         LongBuffer dst =
-                ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType).asLongBuffer();
+                ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType).asLongBuffer();
         long[] src = new long[1024];
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
@@ -489,11 +534,13 @@
     }
 
     @Test
-    public void timeByteBuffer_putShort() throws Exception {
-        ByteBuffer src = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
+    @Parameters(method = "getData")
+    public void timeByteBuffer_putShort(
+            MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
+        ByteBuffer src = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            src.position(mAligned ? 0 : 1);
+            src.position(aligned ? 0 : 1);
             for (int i = 0; i < 1024; ++i) {
                 src.putShort((short) 0);
             }
@@ -501,9 +548,11 @@
     }
 
     @Test
-    public void timeShortBuffer_putShortArray() throws Exception {
+    @Parameters(method = "getData")
+    public void timeShortBuffer_putShortArray(
+            MyByteOrder byteOrder, boolean aligned, MyBufferType bufferType) throws Exception {
         ShortBuffer dst =
-                ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType).asShortBuffer();
+                ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType).asShortBuffer();
         short[] src = new short[1024];
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
@@ -515,6 +564,7 @@
     }
 
     @Test
+    @Parameters(method = "getData")
     public void time_new_byteArray() throws Exception {
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
@@ -523,6 +573,7 @@
     }
 
     @Test
+    @Parameters(method = "getData")
     public void time_ByteBuffer_allocate() throws Exception {
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/ByteBufferScalarVersusVectorPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/ByteBufferScalarVersusVectorPerfTest.java
index 81f9e59..5dd9d6e 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/ByteBufferScalarVersusVectorPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/ByteBufferScalarVersusVectorPerfTest.java
@@ -20,23 +20,23 @@
 import android.perftests.utils.PerfStatusReporter;
 import android.test.suitebuilder.annotation.LargeTest;
 
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
 
 import java.nio.ByteBuffer;
 import java.util.Arrays;
 import java.util.Collection;
 
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
 @LargeTest
 public class ByteBufferScalarVersusVectorPerfTest {
     @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
 
-    @Parameters(name = "mByteOrder={0}, mAligned={1}, mBufferType={2}")
-    public static Collection<Object[]> data() {
+    public static Collection<Object[]> getData() {
         return Arrays.asList(
                 new Object[][] {
                     {
@@ -102,19 +102,15 @@
                 });
     }
 
-    @Parameterized.Parameter(0)
-    public ByteBufferPerfTest.MyByteOrder mByteOrder;
-
-    @Parameterized.Parameter(1)
-    public boolean mAligned;
-
-    @Parameterized.Parameter(2)
-    public ByteBufferPerfTest.MyBufferType mBufferType;
-
     @Test
-    public void timeManualByteBufferCopy() throws Exception {
-        ByteBuffer src = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
-        ByteBuffer dst = ByteBufferPerfTest.newBuffer(mByteOrder, mAligned, mBufferType);
+    @Parameters(method = "getData")
+    public void timeManualByteBufferCopy(
+            ByteBufferPerfTest.MyByteOrder byteOrder,
+            boolean aligned,
+            ByteBufferPerfTest.MyBufferType bufferType)
+            throws Exception {
+        ByteBuffer src = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
+        ByteBuffer dst = ByteBufferPerfTest.newBuffer(byteOrder, aligned, bufferType);
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
             src.position(0);
@@ -126,23 +122,25 @@
     }
 
     @Test
-    public void timeByteBufferBulkGet() throws Exception {
-        ByteBuffer src = ByteBuffer.allocate(mAligned ? 8192 : 8192 + 1);
+    @Parameters({"true", "false"})
+    public void timeByteBufferBulkGet(boolean aligned) throws Exception {
+        ByteBuffer src = ByteBuffer.allocate(aligned ? 8192 : 8192 + 1);
         byte[] dst = new byte[8192];
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            src.position(mAligned ? 0 : 1);
+            src.position(aligned ? 0 : 1);
             src.get(dst, 0, dst.length);
         }
     }
 
     @Test
-    public void timeDirectByteBufferBulkGet() throws Exception {
-        ByteBuffer src = ByteBuffer.allocateDirect(mAligned ? 8192 : 8192 + 1);
+    @Parameters({"true", "false"})
+    public void timeDirectByteBufferBulkGet(boolean aligned) throws Exception {
+        ByteBuffer src = ByteBuffer.allocateDirect(aligned ? 8192 : 8192 + 1);
         byte[] dst = new byte[8192];
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            src.position(mAligned ? 0 : 1);
+            src.position(aligned ? 0 : 1);
             src.get(dst, 0, dst.length);
         }
     }
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/CharacterPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/CharacterPerfTest.java
index 28ec6de..0a59899 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/CharacterPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/CharacterPerfTest.java
@@ -20,12 +20,12 @@
 import android.perftests.utils.PerfStatusReporter;
 import android.test.suitebuilder.annotation.LargeTest;
 
-import org.junit.Before;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
 
 import java.util.Arrays;
 import java.util.Collection;
@@ -34,13 +34,12 @@
  * Tests various Character methods, intended for testing multiple implementations against each
  * other.
  */
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
 @LargeTest
 public class CharacterPerfTest {
     @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
 
-    @Parameters(name = "mCharacterSet({0}), mOverload({1})")
-    public static Collection<Object[]> data() {
+    public static Collection<Object[]> getData() {
         return Arrays.asList(
                 new Object[][] {
                     {CharacterSet.ASCII, Overload.CHAR},
@@ -50,17 +49,10 @@
                 });
     }
 
-    @Parameterized.Parameter(0)
-    public CharacterSet mCharacterSet;
-
-    @Parameterized.Parameter(1)
-    public Overload mOverload;
-
     private char[] mChars;
 
-    @Before
-    public void setUp() throws Exception {
-        this.mChars = mCharacterSet.mChars;
+    public void setUp(CharacterSet characterSet) {
+        this.mChars = characterSet.mChars;
     }
 
     public enum Overload {
@@ -87,10 +79,12 @@
 
     // A fake benchmark to give us a baseline.
     @Test
-    public void timeIsSpace() {
+    @Parameters(method = "getData")
+    public void timeIsSpace(CharacterSet characterSet, Overload overload) {
+        setUp(characterSet);
         boolean fake = false;
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-        if (mOverload == Overload.CHAR) {
+        if (overload == Overload.CHAR) {
             while (state.keepRunning()) {
                 for (int ch = 0; ch < 65536; ++ch) {
                     fake ^= ((char) ch == ' ');
@@ -106,9 +100,11 @@
     }
 
     @Test
-    public void timeDigit() {
+    @Parameters(method = "getData")
+    public void timeDigit(CharacterSet characterSet, Overload overload) {
+        setUp(characterSet);
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-        if (mOverload == Overload.CHAR) {
+        if (overload == Overload.CHAR) {
             while (state.keepRunning()) {
                 for (int ch = 0; ch < 65536; ++ch) {
                     Character.digit(mChars[ch], 10);
@@ -124,9 +120,11 @@
     }
 
     @Test
-    public void timeGetNumericValue() {
+    @Parameters(method = "getData")
+    public void timeGetNumericValue(CharacterSet characterSet, Overload overload) {
+        setUp(characterSet);
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-        if (mOverload == Overload.CHAR) {
+        if (overload == Overload.CHAR) {
             while (state.keepRunning()) {
                 for (int ch = 0; ch < 65536; ++ch) {
                     Character.getNumericValue(mChars[ch]);
@@ -142,9 +140,11 @@
     }
 
     @Test
-    public void timeIsDigit() {
+    @Parameters(method = "getData")
+    public void timeIsDigit(CharacterSet characterSet, Overload overload) {
+        setUp(characterSet);
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-        if (mOverload == Overload.CHAR) {
+        if (overload == Overload.CHAR) {
             while (state.keepRunning()) {
                 for (int ch = 0; ch < 65536; ++ch) {
                     Character.isDigit(mChars[ch]);
@@ -160,9 +160,11 @@
     }
 
     @Test
-    public void timeIsIdentifierIgnorable() {
+    @Parameters(method = "getData")
+    public void timeIsIdentifierIgnorable(CharacterSet characterSet, Overload overload) {
+        setUp(characterSet);
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-        if (mOverload == Overload.CHAR) {
+        if (overload == Overload.CHAR) {
             while (state.keepRunning()) {
                 for (int ch = 0; ch < 65536; ++ch) {
                     Character.isIdentifierIgnorable(mChars[ch]);
@@ -178,9 +180,11 @@
     }
 
     @Test
-    public void timeIsJavaIdentifierPart() {
+    @Parameters(method = "getData")
+    public void timeIsJavaIdentifierPart(CharacterSet characterSet, Overload overload) {
+        setUp(characterSet);
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-        if (mOverload == Overload.CHAR) {
+        if (overload == Overload.CHAR) {
             while (state.keepRunning()) {
                 for (int ch = 0; ch < 65536; ++ch) {
                     Character.isJavaIdentifierPart(mChars[ch]);
@@ -196,9 +200,11 @@
     }
 
     @Test
-    public void timeIsJavaIdentifierStart() {
+    @Parameters(method = "getData")
+    public void timeIsJavaIdentifierStart(CharacterSet characterSet, Overload overload) {
+        setUp(characterSet);
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-        if (mOverload == Overload.CHAR) {
+        if (overload == Overload.CHAR) {
             while (state.keepRunning()) {
                 for (int ch = 0; ch < 65536; ++ch) {
                     Character.isJavaIdentifierStart(mChars[ch]);
@@ -214,9 +220,11 @@
     }
 
     @Test
-    public void timeIsLetter() {
+    @Parameters(method = "getData")
+    public void timeIsLetter(CharacterSet characterSet, Overload overload) {
+        setUp(characterSet);
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-        if (mOverload == Overload.CHAR) {
+        if (overload == Overload.CHAR) {
             while (state.keepRunning()) {
                 for (int ch = 0; ch < 65536; ++ch) {
                     Character.isLetter(mChars[ch]);
@@ -232,9 +240,11 @@
     }
 
     @Test
-    public void timeIsLetterOrDigit() {
+    @Parameters(method = "getData")
+    public void timeIsLetterOrDigit(CharacterSet characterSet, Overload overload) {
+        setUp(characterSet);
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-        if (mOverload == Overload.CHAR) {
+        if (overload == Overload.CHAR) {
             while (state.keepRunning()) {
                 for (int ch = 0; ch < 65536; ++ch) {
                     Character.isLetterOrDigit(mChars[ch]);
@@ -250,9 +260,11 @@
     }
 
     @Test
-    public void timeIsLowerCase() {
+    @Parameters(method = "getData")
+    public void timeIsLowerCase(CharacterSet characterSet, Overload overload) {
+        setUp(characterSet);
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-        if (mOverload == Overload.CHAR) {
+        if (overload == Overload.CHAR) {
             while (state.keepRunning()) {
                 for (int ch = 0; ch < 65536; ++ch) {
                     Character.isLowerCase(mChars[ch]);
@@ -268,9 +280,11 @@
     }
 
     @Test
-    public void timeIsSpaceChar() {
+    @Parameters(method = "getData")
+    public void timeIsSpaceChar(CharacterSet characterSet, Overload overload) {
+        setUp(characterSet);
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-        if (mOverload == Overload.CHAR) {
+        if (overload == Overload.CHAR) {
             while (state.keepRunning()) {
                 for (int ch = 0; ch < 65536; ++ch) {
                     Character.isSpaceChar(mChars[ch]);
@@ -286,9 +300,11 @@
     }
 
     @Test
-    public void timeIsUpperCase() {
+    @Parameters(method = "getData")
+    public void timeIsUpperCase(CharacterSet characterSet, Overload overload) {
+        setUp(characterSet);
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-        if (mOverload == Overload.CHAR) {
+        if (overload == Overload.CHAR) {
             while (state.keepRunning()) {
                 for (int ch = 0; ch < 65536; ++ch) {
                     Character.isUpperCase(mChars[ch]);
@@ -304,9 +320,11 @@
     }
 
     @Test
-    public void timeIsWhitespace() {
+    @Parameters(method = "getData")
+    public void timeIsWhitespace(CharacterSet characterSet, Overload overload) {
+        setUp(characterSet);
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-        if (mOverload == Overload.CHAR) {
+        if (overload == Overload.CHAR) {
             while (state.keepRunning()) {
                 for (int ch = 0; ch < 65536; ++ch) {
                     Character.isWhitespace(mChars[ch]);
@@ -322,9 +340,11 @@
     }
 
     @Test
-    public void timeToLowerCase() {
+    @Parameters(method = "getData")
+    public void timeToLowerCase(CharacterSet characterSet, Overload overload) {
+        setUp(characterSet);
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-        if (mOverload == Overload.CHAR) {
+        if (overload == Overload.CHAR) {
             while (state.keepRunning()) {
                 for (int ch = 0; ch < 65536; ++ch) {
                     Character.toLowerCase(mChars[ch]);
@@ -340,9 +360,11 @@
     }
 
     @Test
-    public void timeToUpperCase() {
+    @Parameters(method = "getData")
+    public void timeToUpperCase(CharacterSet characterSet, Overload overload) {
+        setUp(characterSet);
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-        if (mOverload == Overload.CHAR) {
+        if (overload == Overload.CHAR) {
             while (state.keepRunning()) {
                 for (int ch = 0; ch < 65536; ++ch) {
                     Character.toUpperCase(mChars[ch]);
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/CharsetForNamePerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/CharsetForNamePerfTest.java
index 603b182..8da13a9 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/CharsetForNamePerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/CharsetForNamePerfTest.java
@@ -20,44 +20,40 @@
 import android.perftests.utils.PerfStatusReporter;
 import android.test.suitebuilder.annotation.LargeTest;
 
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
 
 import java.nio.charset.Charset;
-import java.util.Arrays;
-import java.util.Collection;
 
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
 @LargeTest
 public class CharsetForNamePerfTest {
     @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
 
-    @Parameterized.Parameters(name = "mCharsetName({0})")
-    public static Collection<Object[]> data() {
-        return Arrays.asList(
-                new Object[][] {
-                    {"UTF-16"},
-                    {"UTF-8"},
-                    {"UTF8"},
-                    {"ISO-8859-1"},
-                    {"8859_1"},
-                    {"ISO-8859-2"},
-                    {"8859_2"},
-                    {"US-ASCII"},
-                    {"ASCII"},
-                });
+    public static String[] charsetNames() {
+        return new String[] {
+            "UTF-16",
+            "UTF-8",
+            "UTF8",
+            "ISO-8859-1",
+            "8859_1",
+            "ISO-8859-2",
+            "8859_2",
+            "US-ASCII",
+            "ASCII",
+        };
     }
 
-    @Parameterized.Parameter(0)
-    public String mCharsetName;
-
     @Test
-    public void timeCharsetForName() throws Exception {
+    @Parameters(method = "charsetNames")
+    public void timeCharsetForName(String charsetName) throws Exception {
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            Charset.forName(mCharsetName);
+            Charset.forName(charsetName);
         }
     }
 }
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/CharsetPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/CharsetPerfTest.java
index 437d186..048c50f 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/CharsetPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/CharsetPerfTest.java
@@ -20,22 +20,22 @@
 import android.perftests.utils.PerfStatusReporter;
 import android.test.suitebuilder.annotation.LargeTest;
 
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
 
 import java.util.Arrays;
 import java.util.Collection;
 
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
 @LargeTest
 public class CharsetPerfTest {
     @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
 
-    @Parameters(name = "mLength({0}), mName({1})")
-    public static Collection<Object[]> data() {
+    public static Collection<Object[]> getData() {
         return Arrays.asList(
                 new Object[][] {
                     {1, "UTF-16"},
@@ -86,24 +86,20 @@
                 });
     }
 
-    @Parameterized.Parameter(0)
-    public int mLength;
-
-    @Parameterized.Parameter(1)
-    public String mName;
-
     @Test
-    public void time_new_String_BString() throws Exception {
-        byte[] bytes = makeBytes(makeString(mLength));
+    @Parameters(method = "getData")
+    public void time_new_String_BString(int length, String name) throws Exception {
+        byte[] bytes = makeBytes(makeString(length));
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            new String(bytes, mName);
+            new String(bytes, name);
         }
     }
 
     @Test
-    public void time_new_String_BII() throws Exception {
-        byte[] bytes = makeBytes(makeString(mLength));
+    @Parameters(method = "getData")
+    public void time_new_String_BII(int length, String name) throws Exception {
+        byte[] bytes = makeBytes(makeString(length));
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
             new String(bytes, 0, bytes.length);
@@ -111,20 +107,22 @@
     }
 
     @Test
-    public void time_new_String_BIIString() throws Exception {
-        byte[] bytes = makeBytes(makeString(mLength));
+    @Parameters(method = "getData")
+    public void time_new_String_BIIString(int length, String name) throws Exception {
+        byte[] bytes = makeBytes(makeString(length));
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            new String(bytes, 0, bytes.length, mName);
+            new String(bytes, 0, bytes.length, name);
         }
     }
 
     @Test
-    public void time_String_getBytes() throws Exception {
-        String string = makeString(mLength);
+    @Parameters(method = "getData")
+    public void time_String_getBytes(int length, String name) throws Exception {
+        String string = makeString(length);
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            string.getBytes(mName);
+            string.getBytes(name);
         }
     }
 
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/CipherPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/CipherPerfTest.java
index 15c27f2..42b0588 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/CipherPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/CipherPerfTest.java
@@ -20,11 +20,12 @@
 import android.perftests.utils.PerfStatusReporter;
 import android.test.suitebuilder.annotation.LargeTest;
 
-import org.junit.Before;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
 
 import java.security.spec.AlgorithmParameterSpec;
 import java.util.ArrayList;
@@ -42,17 +43,13 @@
  * Cipher benchmarks. Only runs on AES currently because of the combinatorial explosion of the test
  * as it stands.
  */
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
 @LargeTest
 public class CipherPerfTest {
     @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
 
-    @Parameterized.Parameters(
-            name =
-                    "mMode({0}), mPadding({1}), mKeySize({2}), mInputSize({3}),"
-                            + " mImplementation({4})")
-    public static Collection cases() {
-        int[] mKeySizes = new int[] {128, 192, 256};
+    public static Collection getCases() {
+        int[] keySizes = new int[] {128, 192, 256};
         int[] inputSizes = new int[] {16, 32, 64, 128, 1024, 8192};
         final List<Object[]> params = new ArrayList<>();
         for (Mode mode : Mode.values()) {
@@ -71,11 +68,11 @@
                             && implementation == Implementation.OpenSSL) {
                         continue;
                     }
-                    for (int mKeySize : mKeySizes) {
+                    for (int keySize : keySizes) {
                         for (int inputSize : inputSizes) {
                             params.add(
                                     new Object[] {
-                                        mode, padding, mKeySize, inputSize, implementation
+                                        mode, padding, keySize, inputSize, implementation
                                     });
                         }
                     }
@@ -107,9 +104,6 @@
         AES,
     };
 
-    @Parameterized.Parameter(0)
-    public Mode mMode;
-
     public enum Mode {
         CBC,
         CFB,
@@ -118,23 +112,11 @@
         OFB,
     };
 
-    @Parameterized.Parameter(1)
-    public Padding mPadding;
-
     public enum Padding {
         NOPADDING,
         PKCS1PADDING,
     };
 
-    @Parameterized.Parameter(2)
-    public int mKeySize;
-
-    @Parameterized.Parameter(3)
-    public int mInputSize;
-
-    @Parameterized.Parameter(4)
-    public Implementation mImplementation;
-
     public enum Implementation {
         OpenSSL,
         BouncyCastle
@@ -156,21 +138,20 @@
 
     private AlgorithmParameterSpec mSpec;
 
-    @Before
-    public void setUp() throws Exception {
-        mCipherAlgorithm =
-                mAlgorithm.toString() + "/" + mMode.toString() + "/" + mPadding.toString();
+    public void setUp(Mode mode, Padding padding, int keySize, Implementation implementation)
+            throws Exception {
+        mCipherAlgorithm = mAlgorithm.toString() + "/" + mode.toString() + "/" + padding.toString();
 
         String mKeyAlgorithm = mAlgorithm.toString();
-        mKey = sKeySizes.get(mKeySize);
+        mKey = sKeySizes.get(keySize);
         if (mKey == null) {
             KeyGenerator generator = KeyGenerator.getInstance(mKeyAlgorithm);
-            generator.init(mKeySize);
+            generator.init(keySize);
             mKey = generator.generateKey();
-            sKeySizes.put(mKeySize, mKey);
+            sKeySizes.put(keySize, mKey);
         }
 
-        switch (mImplementation) {
+        switch (implementation) {
             case OpenSSL:
                 mProviderName = "AndroidOpenSSL";
                 break;
@@ -178,10 +159,10 @@
                 mProviderName = "BC";
                 break;
             default:
-                throw new RuntimeException(mImplementation.toString());
+                throw new RuntimeException(implementation.toString());
         }
 
-        if (mMode != Mode.ECB) {
+        if (mode != Mode.ECB) {
             mSpec = new IvParameterSpec(IV);
         }
 
@@ -193,18 +174,26 @@
     }
 
     @Test
-    public void timeEncrypt() throws Exception {
+    @Parameters(method = "getCases")
+    public void timeEncrypt(
+            Mode mode, Padding padding, int keySize, int inputSize, Implementation implementation)
+            throws Exception {
+        setUp(mode, padding, keySize, implementation);
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            mCipherEncrypt.doFinal(DATA, 0, mInputSize, mOutput);
+            mCipherEncrypt.doFinal(DATA, 0, inputSize, mOutput);
         }
     }
 
     @Test
-    public void timeDecrypt() throws Exception {
+    @Parameters(method = "getCases")
+    public void timeDecrypt(
+            Mode mode, Padding padding, int keySize, int inputSize, Implementation implementation)
+            throws Exception {
+        setUp(mode, padding, keySize, implementation);
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            mCipherDecrypt.doFinal(DATA, 0, mInputSize, mOutput);
+            mCipherDecrypt.doFinal(DATA, 0, inputSize, mOutput);
         }
     }
 }
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/CollectionsPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/CollectionsPerfTest.java
index a89efff..69197c3 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/CollectionsPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/CollectionsPerfTest.java
@@ -20,11 +20,12 @@
 import android.perftests.utils.PerfStatusReporter;
 import android.test.suitebuilder.annotation.LargeTest;
 
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -35,19 +36,15 @@
 import java.util.Random;
 import java.util.Vector;
 
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
 @LargeTest
 public class CollectionsPerfTest {
     @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
 
-    @Parameters(name = "mArrayListLength({0})")
-    public static Collection<Object[]> data() {
+    public static Collection<Object[]> getData() {
         return Arrays.asList(new Object[][] {{4}, {16}, {64}, {256}, {1024}});
     }
 
-    @Parameterized.Parameter(0)
-    public int arrayListLength;
-
     public static Comparator<Integer> REVERSE =
             new Comparator<Integer>() {
                 @Override
@@ -59,7 +56,8 @@
             };
 
     @Test
-    public void timeSort_arrayList() throws Exception {
+    @Parameters(method = "getData")
+    public void timeSort_arrayList(int arrayListLength) throws Exception {
         List<Integer> input = buildList(arrayListLength, ArrayList.class);
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
@@ -68,7 +66,8 @@
     }
 
     @Test
-    public void timeSortWithComparator_arrayList() throws Exception {
+    @Parameters(method = "getData")
+    public void timeSortWithComparator_arrayList(int arrayListLength) throws Exception {
         List<Integer> input = buildList(arrayListLength, ArrayList.class);
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
@@ -77,7 +76,8 @@
     }
 
     @Test
-    public void timeSort_vector() throws Exception {
+    @Parameters(method = "getData")
+    public void timeSort_vector(int arrayListLength) throws Exception {
         List<Integer> input = buildList(arrayListLength, Vector.class);
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
@@ -86,7 +86,8 @@
     }
 
     @Test
-    public void timeSortWithComparator_vector() throws Exception {
+    @Parameters(method = "getData")
+    public void timeSortWithComparator_vector(int arrayListLength) throws Exception {
         List<Integer> input = buildList(arrayListLength, Vector.class);
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/EqualsHashCodePerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/EqualsHashCodePerfTest.java
index 4ff3ba5..8391203 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/EqualsHashCodePerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/EqualsHashCodePerfTest.java
@@ -20,11 +20,12 @@
 import android.perftests.utils.PerfStatusReporter;
 import android.test.suitebuilder.annotation.LargeTest;
 
-import org.junit.Before;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
 
 import java.net.URI;
 import java.net.URL;
@@ -32,39 +33,39 @@
 import java.util.Collection;
 import java.util.List;
 
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
 @LargeTest
 public final class EqualsHashCodePerfTest {
     @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
 
     private enum Type {
         URI() {
-            @Override Object newInstance(String text) throws Exception {
+            @Override
+            Object newInstance(String text) throws Exception {
                 return new URI(text);
             }
         },
         URL() {
-            @Override Object newInstance(String text) throws Exception {
+            @Override
+            Object newInstance(String text) throws Exception {
                 return new URL(text);
             }
         };
+
         abstract Object newInstance(String text) throws Exception;
     }
 
-    private static final String QUERY = "%E0%AE%A8%E0%AE%BE%E0%AE%AE%E0%AF%8D+%E0%AE%AE%E0%AF%81%E0%AE%95%E0%AF%8D%E0%AE%95%E0%AE%BF%E0%AE%AF%E0%AE%AE%E0%AE%BE%E0%AE%A9%2C+%E0%AE%9A%E0%AF%81%E0%AE%B5%E0%AE%BE%E0%AE%B0%E0%AE%B8%E0%AF%8D%E0%AE%AF%E0%AE%AE%E0%AE%BE%E0%AE%A9+%E0%AE%87%E0%AE%B0%E0%AF%81%E0%AE%AA%E0%AF%8D%E0%AE%AA%E0%AF%87%E0%AE%BE%E0%AE%AE%E0%AF%8D%2C+%E0%AE%86%E0%AE%A9%E0%AE%BE%E0%AE%B2%E0%AF%8D+%E0%AE%9A%E0%AE%BF%E0%AE%B2+%E0%AE%A8%E0%AF%87%E0%AE%B0%E0%AE%99%E0%AF%8D%E0%AE%95%E0%AE%B3%E0%AE%BF%E0%AE%B2%E0%AF%8D+%E0%AE%9A%E0%AF%82%E0%AE%B4%E0%AF%8D%E0%AE%A8%E0%AE%BF%E0%AE%B2%E0%AF%88+%E0%AE%8F%E0%AE%B1%E0%AF%8D%E0%AE%AA%E0%AE%9F%E0%AF%81%E0%AE%AE%E0%AF%8D+%E0%AE%8E%E0%AE%A9%E0%AF%8D%E0%AE%AA%E0%AE%A4%E0%AE%BE%E0%AE%B2%E0%AF%8D+%E0%AE%AA%E0%AE%A3%E0%AE%BF%E0%AE%AF%E0%AF%88%E0%AE%AF%E0%AF%81%E0%AE%AE%E0%AF%8D+%E0%AE%B5%E0%AE%B2%E0%AE%BF+%E0%AE%85%E0%AE%B5%E0%AE%B0%E0%AF%88+%E0%AE%9A%E0%AE%BF%E0%AE%B2+%E0%AE%AA%E0%AF%86%E0%AE%B0%E0%AE%BF%E0%AE%AF+%E0%AE%95%E0%AF%86%E0%AE%BE%E0%AE%B3%E0%AF%8D%E0%AE%AE%E0%AF%81%E0%AE%A4%E0%AE%B2%E0%AF%8D+%E0%AE%AE%E0%AF%81%E0%AE%9F%E0%AE%BF%E0%AE%AF%E0%AF%81%E0%AE%AE%E0%AF%8D.+%E0%AE%85%E0%AE%A4%E0%AF%81+%E0%AE%9A%E0%AE%BF%E0%AE%B2+%E0%AE%A8%E0%AE%A9%E0%AF%8D%E0%AE%AE%E0%AF%88%E0%AE%95%E0%AE%B3%E0%AF%88+%E0%AE%AA%E0%AF%86%E0%AE%B1+%E0%AE%A4%E0%AE%B5%E0%AE%BF%E0%AE%B0%2C+%E0%AE%8E%E0%AE%AA%E0%AF%8D%E0%AE%AA%E0%AF%87%E0%AE%BE%E0%AE%A4%E0%AF%81%E0%AE%AE%E0%AF%8D+%E0%AE%89%E0%AE%B4%E0%AF%88%E0%AE%95%E0%AF%8D%E0%AE%95+%E0%AE%89%E0%AE%9F%E0%AE%B1%E0%AF%8D%E0%AE%AA%E0%AE%AF%E0%AE%BF%E0%AE%B1%E0%AF%8D%E0%AE%9A%E0%AE%BF+%E0%AE%AE%E0%AF%87%E0%AE%B1%E0%AF%8D%E0%AE%95%E0%AF%86%E0%AE%BE%E0%AE%B3%E0%AF%8D%E0%AE%95%E0%AE%BF%E0%AE%B1%E0%AE%A4%E0%AF%81+%E0%AE%8E%E0%AE%99%E0%AF%8D%E0%AE%95%E0%AE%B3%E0%AF%81%E0%AE%95%E0%AF%8D%E0%AE%95%E0%AF%81+%E0%AE%87%E0%AE%A4%E0%AF%81+%E0%AE%92%E0%AE%B0%E0%AF%81+%E0%AE%9A%E0%AE%BF%E0%AE%B1%E0%AE%BF%E0%AE%AF+%E0%AE%89%E0%AE%A4%E0%AE%BE%E0%AE%B0%E0%AE%A3%E0%AE%AE%E0%AF%8D%2C+%E0%AE%8E%E0%AE%9F%E0%AF%81%E0%AE%95%E0%AF%8D%E0%AE%95.+%E0%AE%B0%E0%AE%AF%E0%AE%BF%E0%AE%B2%E0%AF%8D+%E0%AE%8E%E0%AE%A8%E0%AF%8D%E0%AE%A4+%E0%AE%B5%E0%AE%BF%E0%AE%B3%E0%AF%88%E0%AE%B5%E0%AE%BE%E0%AE%95+%E0%AE%87%E0%AE%A9%E0%AF%8D%E0%AE%AA%E0%AE%AE%E0%AF%8D+%E0%AE%86%E0%AE%A9%E0%AF%8D%E0%AE%B2%E0%AF%88%E0%AE%A9%E0%AF%8D+%E0%AE%AA%E0%AE%AF%E0%AE%A9%E0%AF%8D%E0%AE%AA%E0%AE%BE%E0%AE%9F%E0%AF%81%E0%AE%95%E0%AE%B3%E0%AF%8D+%E0%AE%87%E0%AE%B0%E0%AF%81%E0%AE%95%E0%AF%8D%E0%AE%95+%E0%AE%B5%E0%AF%87%E0%AE%A3%E0%AF%8D%E0%AE%9F%E0%AF%81%E0%AE%AE%E0%AF%8D+%E0%AE%A4%E0%AE%AF%E0%AE%BE%E0%AE%B0%E0%AE%BF%E0%AE%95%E0%AF%8D%E0%AE%95%E0%AF%81%E0%AE%AE%E0%AF%8D+%E0%AE%A4%E0%AE%B5%E0%AE%B1%E0%AF%81+%E0%AE%95%E0%AE%A3%E0%AF%8D%E0%AE%9F%E0%AF%81%E0%AE%AA%E0%AE%BF%E0%AE%9F%E0%AE%BF%E0%AE%95%E0%AF%8D%E0%AE%95+%E0%AE%B5%E0%AE%B0%E0%AF%81%E0%AE%AE%E0%AF%8D+%E0%AE%A8%E0%AE%BE%E0%AE%AE%E0%AF%8D+%E0%AE%A4%E0%AE%B1%E0%AF%8D%E0%AE%AA%E0%AF%87%E0%AE%BE%E0%AE%A4%E0%AF%81+%E0%AE%87%E0%AE%B0%E0%AF%81%E0%AE%95%E0%AF%8D%E0%AE%95%E0%AE%BF%E0%AE%B1%E0%AF%87%E0%AE%BE%E0%AE%AE%E0%AF%8D.+%E0%AE%87%E0%AE%A8%E0%AF%8D%E0%AE%A4+%E0%AE%A8%E0%AE%BF%E0%AE%95%E0%AE%B4%E0%AF%8D%E0%AE%B5%E0%AF%81%E0%AE%95%E0%AE%B3%E0%AE%BF%E0%AE%B2%E0%AF%8D+%E0%AE%9A%E0%AF%86%E0%AE%AF%E0%AF%8D%E0%AE%A4%E0%AE%AA%E0%AE%BF%E0%AE%A9%E0%AF%8D+%E0%AE%85%E0%AE%AE%E0%AF%88%E0%AE%AA%E0%AF%8D%E0%AE%AA%E0%AE%BF%E0%AE%A9%E0%AF%8D+%E0%AE%95%E0%AE%A3%E0%AE%95%E0%AF%8D%E0%AE%95%E0%AF%81%2C+%E0%AE%85%E0%AE%B5%E0%AE%B0%E0%AF%8D%E0%AE%95%E0%AE%B3%E0%AF%8D+%E0%AE%A4%E0%AE%B5%E0%AE%B1%E0%AF%81+%E0%AE%B5%E0%AE%BF%E0%AE%9F%E0%AF%8D%E0%AE%9F%E0%AF%81+quae+%E0%AE%AA%E0%AE%9F%E0%AF%8D%E0%AE%9F%E0%AE%B1%E0%AF%88+%E0%AE%A8%E0%AF%80%E0%AE%99%E0%AF%8D%E0%AE%95%E0%AE%B3%E0%AF%8D+%E0%AE%AA%E0%AE%B0%E0%AE%BF%E0%AE%A8%E0%AF%8D%E0%AE%A4%E0%AF%81%E0%AE%B0%E0%AF%88%E0%AE%95%E0%AF%8D%E0%AE%95%E0%AE%BF%E0%AE%B1%E0%AF%87%E0%AE%BE%E0%AE%AE%E0%AF%8D+%E0%AE%AE%E0%AF%86%E0%AE%A9%E0%AF%8D%E0%AE%AE%E0%AF%88%E0%AE%AF%E0%AE%BE%E0%AE%95+%E0%AE%AE%E0%AE%BE%E0%AE%B1%E0%AF%81%E0%AE%AE%E0%AF%8D";
+    private static final String QUERY =
+            "%E0%AE%A8%E0%AE%BE%E0%AE%AE%E0%AF%8D+%E0%AE%AE%E0%AF%81%E0%AE%95%E0%AF%8D%E0%AE%95%E0%AE%BF%E0%AE%AF%E0%AE%AE%E0%AE%BE%E0%AE%A9%2C+%E0%AE%9A%E0%AF%81%E0%AE%B5%E0%AE%BE%E0%AE%B0%E0%AE%B8%E0%AF%8D%E0%AE%AF%E0%AE%AE%E0%AE%BE%E0%AE%A9+%E0%AE%87%E0%AE%B0%E0%AF%81%E0%AE%AA%E0%AF%8D%E0%AE%AA%E0%AF%87%E0%AE%BE%E0%AE%AE%E0%AF%8D%2C+%E0%AE%86%E0%AE%A9%E0%AE%BE%E0%AE%B2%E0%AF%8D+%E0%AE%9A%E0%AE%BF%E0%AE%B2+%E0%AE%A8%E0%AF%87%E0%AE%B0%E0%AE%99%E0%AF%8D%E0%AE%95%E0%AE%B3%E0%AE%BF%E0%AE%B2%E0%AF%8D+%E0%AE%9A%E0%AF%82%E0%AE%B4%E0%AF%8D%E0%AE%A8%E0%AE%BF%E0%AE%B2%E0%AF%88+%E0%AE%8F%E0%AE%B1%E0%AF%8D%E0%AE%AA%E0%AE%9F%E0%AF%81%E0%AE%AE%E0%AF%8D+%E0%AE%8E%E0%AE%A9%E0%AF%8D%E0%AE%AA%E0%AE%A4%E0%AE%BE%E0%AE%B2%E0%AF%8D+%E0%AE%AA%E0%AE%A3%E0%AE%BF%E0%AE%AF%E0%AF%88%E0%AE%AF%E0%AF%81%E0%AE%AE%E0%AF%8D+%E0%AE%B5%E0%AE%B2%E0%AE%BF+%E0%AE%85%E0%AE%B5%E0%AE%B0%E0%AF%88+%E0%AE%9A%E0%AE%BF%E0%AE%B2+%E0%AE%AA%E0%AF%86%E0%AE%B0%E0%AE%BF%E0%AE%AF+%E0%AE%95%E0%AF%86%E0%AE%BE%E0%AE%B3%E0%AF%8D%E0%AE%AE%E0%AF%81%E0%AE%A4%E0%AE%B2%E0%AF%8D+%E0%AE%AE%E0%AF%81%E0%AE%9F%E0%AE%BF%E0%AE%AF%E0%AF%81%E0%AE%AE%E0%AF%8D.+%E0%AE%85%E0%AE%A4%E0%AF%81+%E0%AE%9A%E0%AE%BF%E0%AE%B2+%E0%AE%A8%E0%AE%A9%E0%AF%8D%E0%AE%AE%E0%AF%88%E0%AE%95%E0%AE%B3%E0%AF%88+%E0%AE%AA%E0%AF%86%E0%AE%B1+%E0%AE%A4%E0%AE%B5%E0%AE%BF%E0%AE%B0%2C+%E0%AE%8E%E0%AE%AA%E0%AF%8D%E0%AE%AA%E0%AF%87%E0%AE%BE%E0%AE%A4%E0%AF%81%E0%AE%AE%E0%AF%8D+%E0%AE%89%E0%AE%B4%E0%AF%88%E0%AE%95%E0%AF%8D%E0%AE%95+%E0%AE%89%E0%AE%9F%E0%AE%B1%E0%AF%8D%E0%AE%AA%E0%AE%AF%E0%AE%BF%E0%AE%B1%E0%AF%8D%E0%AE%9A%E0%AE%BF+%E0%AE%AE%E0%AF%87%E0%AE%B1%E0%AF%8D%E0%AE%95%E0%AF%86%E0%AE%BE%E0%AE%B3%E0%AF%8D%E0%AE%95%E0%AE%BF%E0%AE%B1%E0%AE%A4%E0%AF%81+%E0%AE%8E%E0%AE%99%E0%AF%8D%E0%AE%95%E0%AE%B3%E0%AF%81%E0%AE%95%E0%AF%8D%E0%AE%95%E0%AF%81+%E0%AE%87%E0%AE%A4%E0%AF%81+%E0%AE%92%E0%AE%B0%E0%AF%81+%E0%AE%9A%E0%AE%BF%E0%AE%B1%E0%AE%BF%E0%AE%AF+%E0%AE%89%E0%AE%A4%E0%AE%BE%E0%AE%B0%E0%AE%A3%E0%AE%AE%E0%AF%8D%2C+%E0%AE%8E%E0%AE%9F%E0%AF%81%E0%AE%95%E0%AF%8D%E0%AE%95.+%E0%AE%B0%E0%AE%AF%E0%AE%BF%E0%AE%B2%E0%AF%8D+%E0%AE%8E%E0%AE%A8%E0%AF%8D%E0%AE%A4+%E0%AE%B5%E0%AE%BF%E0%AE%B3%E0%AF%88%E0%AE%B5%E0%AE%BE%E0%AE%95+%E0%AE%87%E0%AE%A9%E0%AF%8D%E0%AE%AA%E0%AE%AE%E0%AF%8D+%E0%AE%86%E0%AE%A9%E0%AF%8D%E0%AE%B2%E0%AF%88%E0%AE%A9%E0%AF%8D+%E0%AE%AA%E0%AE%AF%E0%AE%A9%E0%AF%8D%E0%AE%AA%E0%AE%BE%E0%AE%9F%E0%AF%81%E0%AE%95%E0%AE%B3%E0%AF%8D+%E0%AE%87%E0%AE%B0%E0%AF%81%E0%AE%95%E0%AF%8D%E0%AE%95+%E0%AE%B5%E0%AF%87%E0%AE%A3%E0%AF%8D%E0%AE%9F%E0%AF%81%E0%AE%AE%E0%AF%8D+%E0%AE%A4%E0%AE%AF%E0%AE%BE%E0%AE%B0%E0%AE%BF%E0%AE%95%E0%AF%8D%E0%AE%95%E0%AF%81%E0%AE%AE%E0%AF%8D+%E0%AE%A4%E0%AE%B5%E0%AE%B1%E0%AF%81+%E0%AE%95%E0%AE%A3%E0%AF%8D%E0%AE%9F%E0%AF%81%E0%AE%AA%E0%AE%BF%E0%AE%9F%E0%AE%BF%E0%AE%95%E0%AF%8D%E0%AE%95+%E0%AE%B5%E0%AE%B0%E0%AF%81%E0%AE%AE%E0%AF%8D+%E0%AE%A8%E0%AE%BE%E0%AE%AE%E0%AF%8D+%E0%AE%A4%E0%AE%B1%E0%AF%8D%E0%AE%AA%E0%AF%87%E0%AE%BE%E0%AE%A4%E0%AF%81+%E0%AE%87%E0%AE%B0%E0%AF%81%E0%AE%95%E0%AF%8D%E0%AE%95%E0%AE%BF%E0%AE%B1%E0%AF%87%E0%AE%BE%E0%AE%AE%E0%AF%8D.+%E0%AE%87%E0%AE%A8%E0%AF%8D%E0%AE%A4+%E0%AE%A8%E0%AE%BF%E0%AE%95%E0%AE%B4%E0%AF%8D%E0%AE%B5%E0%AF%81%E0%AE%95%E0%AE%B3%E0%AE%BF%E0%AE%B2%E0%AF%8D+%E0%AE%9A%E0%AF%86%E0%AE%AF%E0%AF%8D%E0%AE%A4%E0%AE%AA%E0%AE%BF%E0%AE%A9%E0%AF%8D+%E0%AE%85%E0%AE%AE%E0%AF%88%E0%AE%AA%E0%AF%8D%E0%AE%AA%E0%AE%BF%E0%AE%A9%E0%AF%8D+%E0%AE%95%E0%AE%A3%E0%AE%95%E0%AF%8D%E0%AE%95%E0%AF%81%2C+%E0%AE%85%E0%AE%B5%E0%AE%B0%E0%AF%8D%E0%AE%95%E0%AE%B3%E0%AF%8D+%E0%AE%A4%E0%AE%B5%E0%AE%B1%E0%AF%81+%E0%AE%B5%E0%AE%BF%E0%AE%9F%E0%AF%8D%E0%AE%9F%E0%AF%81+quae+%E0%AE%AA%E0%AE%9F%E0%AF%8D%E0%AE%9F%E0%AE%B1%E0%AF%88+%E0%AE%A8%E0%AF%80%E0%AE%99%E0%AF%8D%E0%AE%95%E0%AE%B3%E0%AF%8D+%E0%AE%AA%E0%AE%B0%E0%AE%BF%E0%AE%A8%E0%AF%8D%E0%AE%A4%E0%AF%81%E0%AE%B0%E0%AF%88%E0%AE%95%E0%AF%8D%E0%AE%95%E0%AE%BF%E0%AE%B1%E0%AF%87%E0%AE%BE%E0%AE%AE%E0%AF%8D+%E0%AE%AE%E0%AF%86%E0%AE%A9%E0%AF%8D%E0%AE%AE%E0%AF%88%E0%AE%AF%E0%AE%BE%E0%AE%95+%E0%AE%AE%E0%AE%BE%E0%AE%B1%E0%AF%81%E0%AE%AE%E0%AF%8D";
 
-    @Parameterized.Parameters(name = "mType({0})")
-    public static Collection cases() {
+    public static Collection getCases() {
         final List<Object[]> params = new ArrayList<>();
         for (Type type : Type.values()) {
-            params.add(new Object[]{type});
+            params.add(new Object[] {type});
         }
         return params;
     }
 
-    @Parameterized.Parameter(0)
-    public Type mType;
-
     Object mA1;
     Object mA2;
     Object mB1;
@@ -73,20 +74,13 @@
     Object mC1;
     Object mC2;
 
-    @Before
-    public void setUp() throws Exception {
-        mA1 = mType.newInstance("https://mail.google.com/mail/u/0/?shva=1#inbox");
-        mA2 = mType.newInstance("https://mail.google.com/mail/u/0/?shva=1#inbox");
-        mB1 = mType.newInstance("http://developer.android.com/reference/java/net/URI.html");
-        mB2 = mType.newInstance("http://developer.android.com/reference/java/net/URI.html");
-
-        mC1 = mType.newInstance("http://developer.android.com/query?q=" + QUERY);
-        // Replace the very last char.
-        mC2 = mType.newInstance("http://developer.android.com/query?q=" + QUERY.substring(0, QUERY.length() - 3) + "%AF");
-    }
-
     @Test
-    public void timeEquals() {
+    @Parameters(method = "getCases")
+    public void timeEquals(Type type) throws Exception {
+        mA1 = type.newInstance("https://mail.google.com/mail/u/0/?shva=1#inbox");
+        mA2 = type.newInstance("https://mail.google.com/mail/u/0/?shva=1#inbox");
+        mB1 = type.newInstance("http://developer.android.com/reference/java/net/URI.html");
+        mB2 = type.newInstance("http://developer.android.com/reference/java/net/URI.html");
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
             mA1.equals(mB1);
@@ -96,7 +90,10 @@
     }
 
     @Test
-    public void timeHashCode() {
+    @Parameters(method = "getCases")
+    public void timeHashCode(Type type) throws Exception {
+        mA1 = type.newInstance("https://mail.google.com/mail/u/0/?shva=1#inbox");
+        mB1 = type.newInstance("http://developer.android.com/reference/java/net/URI.html");
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
             mA1.hashCode();
@@ -105,7 +102,15 @@
     }
 
     @Test
-    public void timeEqualsWithHeavilyEscapedComponent() {
+    @Parameters(method = "getCases")
+    public void timeEqualsWithHeavilyEscapedComponent(Type type) throws Exception {
+        mC1 = type.newInstance("http://developer.android.com/query?q=" + QUERY);
+        // Replace the very last char.
+        mC2 =
+                type.newInstance(
+                        "http://developer.android.com/query?q="
+                                + QUERY.substring(0, QUERY.length() - 3)
+                                + "%AF");
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
             mC1.equals(mC2);
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/KeyPairGeneratorPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/KeyPairGeneratorPerfTest.java
index 6fe9059..80c4487 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/KeyPairGeneratorPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/KeyPairGeneratorPerfTest.java
@@ -20,26 +20,24 @@
 import android.perftests.utils.PerfStatusReporter;
 import android.test.suitebuilder.annotation.LargeTest;
 
-import org.junit.Before;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
 
 import java.security.KeyPair;
 import java.security.KeyPairGenerator;
-import java.security.SecureRandom;
 import java.util.Arrays;
 import java.util.Collection;
 
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
 @LargeTest
 public class KeyPairGeneratorPerfTest {
     @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
 
-    @Parameters(name = "mAlgorithm={0}, mImplementation={1}")
-    public static Collection<Object[]> data() {
+    public static Collection<Object[]> getData() {
         return Arrays.asList(
                 new Object[][] {
                     {Algorithm.RSA, Implementation.BouncyCastle},
@@ -48,12 +46,6 @@
                 });
     }
 
-    @Parameterized.Parameter(0)
-    public Algorithm mAlgorithm;
-
-    @Parameterized.Parameter(1)
-    public Implementation mImplementation;
-
     public enum Algorithm {
         RSA,
         DSA,
@@ -66,26 +58,25 @@
 
     private String mGeneratorAlgorithm;
     private KeyPairGenerator mGenerator;
-    private SecureRandom mRandom;
 
-    @Before
-    public void setUp() throws Exception {
-        this.mGeneratorAlgorithm = mAlgorithm.toString();
+    public void setUp(Algorithm algorithm, Implementation implementation) throws Exception {
+        this.mGeneratorAlgorithm = algorithm.toString();
 
         final String provider;
-        if (mImplementation == Implementation.BouncyCastle) {
+        if (implementation == Implementation.BouncyCastle) {
             provider = "BC";
         } else {
             provider = "AndroidOpenSSL";
         }
 
         this.mGenerator = KeyPairGenerator.getInstance(mGeneratorAlgorithm, provider);
-        this.mRandom = SecureRandom.getInstance("SHA1PRNG");
         this.mGenerator.initialize(1024);
     }
 
     @Test
-    public void time() throws Exception {
+    @Parameters(method = "getData")
+    public void time(Algorithm algorithm, Implementation implementation) throws Exception {
+        setUp(algorithm, implementation);
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
             KeyPair keyPair = mGenerator.generateKeyPair();
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/LoopingBackwardsPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/LoopingBackwardsPerfTest.java
index 414764d..c9b0cbe 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/LoopingBackwardsPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/LoopingBackwardsPerfTest.java
@@ -20,11 +20,12 @@
 import android.perftests.utils.PerfStatusReporter;
 import android.test.suitebuilder.annotation.LargeTest;
 
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
 
 import java.util.Arrays;
 import java.util.Collection;
@@ -34,36 +35,34 @@
  *
  * @author Kevin Bourrillion
  */
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
 @LargeTest
 public class LoopingBackwardsPerfTest {
     @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
 
-    @Parameters(name = "mMax={0}")
-    public static Collection<Object[]> data() {
+    public static Collection<Object[]> getData() {
         return Arrays.asList(new Object[][] {{2}, {20}, {2000}, {20000000}});
     }
 
-    @Parameterized.Parameter(0)
-    public int mMax;
-
     @Test
-    public void timeForwards() {
+    @Parameters(method = "getData")
+    public void timeForwards(int max) {
         int fake = 0;
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            for (int j = 0; j < mMax; j++) {
+            for (int j = 0; j < max; j++) {
                 fake += j;
             }
         }
     }
 
     @Test
-    public void timeBackwards() {
+    @Parameters(method = "getData")
+    public void timeBackwards(int max) {
         int fake = 0;
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            for (int j = mMax - 1; j >= 0; j--) {
+            for (int j = max - 1; j >= 0; j--) {
                 fake += j;
             }
         }
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/MessageDigestPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/MessageDigestPerfTest.java
index 279681b..2dc947a 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/MessageDigestPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/MessageDigestPerfTest.java
@@ -20,24 +20,24 @@
 import android.perftests.utils.PerfStatusReporter;
 import android.test.suitebuilder.annotation.LargeTest;
 
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
 
 import java.nio.ByteBuffer;
 import java.security.MessageDigest;
 import java.util.Arrays;
 import java.util.Collection;
 
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
 @LargeTest
 public class MessageDigestPerfTest {
     @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
 
-    @Parameters(name = "mAlgorithm={0}")
-    public static Collection<Object[]> data() {
+    public static Collection<Object[]> getData() {
         return Arrays.asList(
                 new Object[][] {
                     {Algorithm.MD5},
@@ -48,9 +48,6 @@
                 });
     }
 
-    @Parameterized.Parameter(0)
-    public Algorithm mAlgorithm;
-
     public String mProvider = "AndroidOpenSSL";
 
     private static final int DATA_SIZE = 8192;
@@ -97,44 +94,44 @@
     };
 
     @Test
-    public void time() throws Exception {
+    @Parameters(method = "getData")
+    public void time(Algorithm algorithm) throws Exception {
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            MessageDigest digest =
-                    MessageDigest.getInstance(mAlgorithm.toString(), mProvider);
+            MessageDigest digest = MessageDigest.getInstance(algorithm.toString(), mProvider);
             digest.update(DATA, 0, DATA_SIZE);
             digest.digest();
         }
     }
 
     @Test
-    public void timeLargeArray() throws Exception {
+    @Parameters(method = "getData")
+    public void timeLargeArray(Algorithm algorithm) throws Exception {
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            MessageDigest digest =
-                    MessageDigest.getInstance(mAlgorithm.toString(), mProvider);
+            MessageDigest digest = MessageDigest.getInstance(algorithm.toString(), mProvider);
             digest.update(LARGE_DATA, 0, LARGE_DATA_SIZE);
             digest.digest();
         }
     }
 
     @Test
-    public void timeSmallChunkOfLargeArray() throws Exception {
+    @Parameters(method = "getData")
+    public void timeSmallChunkOfLargeArray(Algorithm algorithm) throws Exception {
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            MessageDigest digest =
-                    MessageDigest.getInstance(mAlgorithm.toString(), mProvider);
+            MessageDigest digest = MessageDigest.getInstance(algorithm.toString(), mProvider);
             digest.update(LARGE_DATA, LARGE_DATA_SIZE / 2, DATA_SIZE);
             digest.digest();
         }
     }
 
     @Test
-    public void timeSmallByteBuffer() throws Exception {
+    @Parameters(method = "getData")
+    public void timeSmallByteBuffer(Algorithm algorithm) throws Exception {
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            MessageDigest digest =
-                    MessageDigest.getInstance(mAlgorithm.toString(), mProvider);
+            MessageDigest digest = MessageDigest.getInstance(algorithm.toString(), mProvider);
             SMALL_BUFFER.position(0);
             SMALL_BUFFER.limit(SMALL_BUFFER.capacity());
             digest.update(SMALL_BUFFER);
@@ -143,11 +140,11 @@
     }
 
     @Test
-    public void timeSmallDirectByteBuffer() throws Exception {
+    @Parameters(method = "getData")
+    public void timeSmallDirectByteBuffer(Algorithm algorithm) throws Exception {
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            MessageDigest digest =
-                    MessageDigest.getInstance(mAlgorithm.toString(), mProvider);
+            MessageDigest digest = MessageDigest.getInstance(algorithm.toString(), mProvider);
             SMALL_DIRECT_BUFFER.position(0);
             SMALL_DIRECT_BUFFER.limit(SMALL_DIRECT_BUFFER.capacity());
             digest.update(SMALL_DIRECT_BUFFER);
@@ -156,11 +153,11 @@
     }
 
     @Test
-    public void timeLargeByteBuffer() throws Exception {
+    @Parameters(method = "getData")
+    public void timeLargeByteBuffer(Algorithm algorithm) throws Exception {
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            MessageDigest digest =
-                    MessageDigest.getInstance(mAlgorithm.toString(), mProvider);
+            MessageDigest digest = MessageDigest.getInstance(algorithm.toString(), mProvider);
             LARGE_BUFFER.position(0);
             LARGE_BUFFER.limit(LARGE_BUFFER.capacity());
             digest.update(LARGE_BUFFER);
@@ -169,11 +166,11 @@
     }
 
     @Test
-    public void timeLargeDirectByteBuffer() throws Exception {
+    @Parameters(method = "getData")
+    public void timeLargeDirectByteBuffer(Algorithm algorithm) throws Exception {
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            MessageDigest digest =
-                    MessageDigest.getInstance(mAlgorithm.toString(), mProvider);
+            MessageDigest digest = MessageDigest.getInstance(algorithm.toString(), mProvider);
             LARGE_DIRECT_BUFFER.position(0);
             LARGE_DIRECT_BUFFER.limit(LARGE_DIRECT_BUFFER.capacity());
             digest.update(LARGE_DIRECT_BUFFER);
@@ -182,11 +179,11 @@
     }
 
     @Test
-    public void timeSmallChunkOfLargeByteBuffer() throws Exception {
+    @Parameters(method = "getData")
+    public void timeSmallChunkOfLargeByteBuffer(Algorithm algorithm) throws Exception {
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            MessageDigest digest =
-                    MessageDigest.getInstance(mAlgorithm.toString(), mProvider);
+            MessageDigest digest = MessageDigest.getInstance(algorithm.toString(), mProvider);
             LARGE_BUFFER.position(LARGE_BUFFER.capacity() / 2);
             LARGE_BUFFER.limit(LARGE_BUFFER.position() + DATA_SIZE);
             digest.update(LARGE_BUFFER);
@@ -195,11 +192,11 @@
     }
 
     @Test
-    public void timeSmallChunkOfLargeDirectByteBuffer() throws Exception {
+    @Parameters(method = "getData")
+    public void timeSmallChunkOfLargeDirectByteBuffer(Algorithm algorithm) throws Exception {
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            MessageDigest digest =
-                    MessageDigest.getInstance(mAlgorithm.toString(), mProvider);
+            MessageDigest digest = MessageDigest.getInstance(algorithm.toString(), mProvider);
             LARGE_DIRECT_BUFFER.position(LARGE_DIRECT_BUFFER.capacity() / 2);
             LARGE_DIRECT_BUFFER.limit(LARGE_DIRECT_BUFFER.position() + DATA_SIZE);
             digest.update(LARGE_DIRECT_BUFFER);
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/MutableIntPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/MutableIntPerfTest.java
index 37bd73c..d9d4bb5 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/MutableIntPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/MutableIntPerfTest.java
@@ -20,17 +20,18 @@
 import android.perftests.utils.PerfStatusReporter;
 import android.test.suitebuilder.annotation.LargeTest;
 
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
 
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.concurrent.atomic.AtomicInteger;
 
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
 @LargeTest
 public final class MutableIntPerfTest {
     @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
@@ -96,29 +97,28 @@
         abstract int timeGet(BenchmarkState state);
     }
 
-    @Parameters(name = "mKind={0}")
-    public static Collection<Object[]> data() {
+    public static Collection<Object[]> getData() {
         return Arrays.asList(new Object[][] {{Kind.ARRAY}, {Kind.ATOMIC}});
     }
 
-    @Parameterized.Parameter(0)
-    public Kind mKind;
-
     @Test
-    public void timeCreate() {
+    @Parameters(method = "getData")
+    public void timeCreate(Kind kind) {
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-        mKind.timeCreate(state);
+        kind.timeCreate(state);
     }
 
     @Test
-    public void timeIncrement() {
+    @Parameters(method = "getData")
+    public void timeIncrement(Kind kind) {
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-        mKind.timeIncrement(state);
+        kind.timeIncrement(state);
     }
 
     @Test
-    public void timeGet() {
+    @Parameters(method = "getData")
+    public void timeGet(Kind kind) {
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
-        mKind.timeGet(state);
+        kind.timeGet(state);
     }
 }
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/PriorityQueuePerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/PriorityQueuePerfTest.java
index 8801a56..48450b4 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/PriorityQueuePerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/PriorityQueuePerfTest.java
@@ -20,12 +20,12 @@
 import android.perftests.utils.PerfStatusReporter;
 import android.test.suitebuilder.annotation.LargeTest;
 
-import org.junit.Before;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -35,13 +35,12 @@
 import java.util.PriorityQueue;
 import java.util.Random;
 
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
 @LargeTest
 public class PriorityQueuePerfTest {
     @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
 
-    @Parameters(name = "mQueueSize={0}, mHitRate={1}")
-    public static Collection<Object[]> data() {
+    public static Collection<Object[]> getData() {
         return Arrays.asList(
                 new Object[][] {
                     {100, 0},
@@ -62,26 +61,19 @@
                 });
     }
 
-    @Parameterized.Parameter(0)
-    public int mQueueSize;
-
-    @Parameterized.Parameter(1)
-    public int mHitRate;
-
     private PriorityQueue<Integer> mPq;
     private PriorityQueue<Integer> mUsepq;
     private List<Integer> mSeekElements;
     private Random mRandom = new Random(189279387L);
 
-    @Before
-    public void setUp() throws Exception {
+    public void setUp(int queueSize, int hitRate) throws Exception {
         mPq = new PriorityQueue<Integer>();
         mUsepq = new PriorityQueue<Integer>();
         mSeekElements = new ArrayList<Integer>();
         List<Integer> allElements = new ArrayList<Integer>();
-        int numShared = (int) (mQueueSize * ((double) mHitRate / 100));
-        // the total number of elements we require to engineer a hit rate of mHitRate%
-        int totalElements = 2 * mQueueSize - numShared;
+        int numShared = (int) (queueSize * ((double) hitRate / 100));
+        // the total number of elements we require to engineer a hit rate of hitRate%
+        int totalElements = 2 * queueSize - numShared;
         for (int i = 0; i < totalElements; i++) {
             allElements.add(i);
         }
@@ -93,11 +85,11 @@
             mSeekElements.add(allElements.get(i));
         }
         // add priority queue only elements (these won't be touched)
-        for (int i = numShared; i < mQueueSize; i++) {
+        for (int i = numShared; i < queueSize; i++) {
             mPq.add(allElements.get(i));
         }
         // add non-priority queue elements (these will be misses)
-        for (int i = mQueueSize; i < totalElements; i++) {
+        for (int i = queueSize; i < totalElements; i++) {
             mSeekElements.add(allElements.get(i));
         }
         mUsepq = new PriorityQueue<Integer>(mPq);
@@ -107,16 +99,18 @@
     }
 
     @Test
-    public void timeRemove() {
+    @Parameters(method = "getData")
+    public void timeRemove(int queueSize, int hitRate) throws Exception {
+        setUp(queueSize, hitRate);
         boolean fake = false;
         int elementsSize = mSeekElements.size();
         // At most allow the queue to empty 10%.
-        int resizingThreshold = mQueueSize / 10;
+        int resizingThreshold = queueSize / 10;
         int i = 0;
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
             // Reset queue every so often. This will be called more often for smaller
-            // mQueueSizes, but since a copy is linear, it will also cost proportionally
+            // queueSizes, but since a copy is linear, it will also cost proportionally
             // less, and hopefully it will approximately balance out.
             if (++i % resizingThreshold == 0) {
                 mUsepq = new PriorityQueue<Integer>(mPq);
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/SchemePrefixPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/SchemePrefixPerfTest.java
index 42dc581..5ad62de 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/SchemePrefixPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/SchemePrefixPerfTest.java
@@ -20,11 +20,12 @@
 import android.perftests.utils.PerfStatusReporter;
 import android.test.suitebuilder.annotation.LargeTest;
 
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
 
 import java.util.Arrays;
 import java.util.Collection;
@@ -32,7 +33,7 @@
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
 @LargeTest
 public final class SchemePrefixPerfTest {
     @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
@@ -85,19 +86,16 @@
         abstract String execute(String spec);
     }
 
-    @Parameters(name = "mStrategy={0}")
-    public static Collection<Object[]> data() {
+    public static Collection<Object[]> getData() {
         return Arrays.asList(new Object[][] {{Strategy.REGEX}, {Strategy.JAVA}});
     }
 
-    @Parameterized.Parameter(0)
-    public Strategy mStrategy;
-
     @Test
-    public void timeSchemePrefix() {
+    @Parameters(method = "getData")
+    public void timeSchemePrefix(Strategy strategy) {
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            mStrategy.execute("http://android.com");
+            strategy.execute("http://android.com");
         }
     }
 }
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/SignaturePerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/SignaturePerfTest.java
index 96e7cb2..a9a0788 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/SignaturePerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/SignaturePerfTest.java
@@ -19,12 +19,12 @@
 import android.perftests.utils.PerfStatusReporter;
 import android.test.suitebuilder.annotation.LargeTest;
 
-import org.junit.Before;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
 
 import java.security.KeyPair;
 import java.security.KeyPairGenerator;
@@ -37,13 +37,12 @@
 import java.util.Map;
 
 /** Tests RSA and DSA mSignature creation and verification. */
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
 @LargeTest
 public class SignaturePerfTest {
     @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
 
-    @Parameters(name = "mAlgorithm={0}, mImplementation={1}")
-    public static Collection<Object[]> data() {
+    public static Collection<Object[]> getData() {
         return Arrays.asList(
                 new Object[][] {
                     {Algorithm.MD5WithRSA, Implementation.OpenSSL},
@@ -55,12 +54,6 @@
                 });
     }
 
-    @Parameterized.Parameter(0)
-    public Algorithm mAlgorithm;
-
-    @Parameterized.Parameter(1)
-    public Implementation mImplementation;
-
     private static final int DATA_SIZE = 8192;
     private static final byte[] DATA = new byte[DATA_SIZE];
 
@@ -94,9 +87,8 @@
     private PrivateKey mPrivateKey;
     private PublicKey mPublicKey;
 
-    @Before
-    public void setUp() throws Exception {
-        this.mSignatureAlgorithm = mAlgorithm.toString();
+    public void setUp(Algorithm algorithm) throws Exception {
+        this.mSignatureAlgorithm = algorithm.toString();
 
         String keyAlgorithm =
                 mSignatureAlgorithm.substring(
@@ -121,11 +113,13 @@
     }
 
     @Test
-    public void timeSign() throws Exception {
+    @Parameters(method = "getData")
+    public void timeSign(Algorithm algorithm, Implementation implementation) throws Exception {
+        setUp(algorithm);
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
             Signature signer;
-            switch (mImplementation) {
+            switch (implementation) {
                 case OpenSSL:
                     signer = Signature.getInstance(mSignatureAlgorithm, "AndroidOpenSSL");
                     break;
@@ -133,7 +127,7 @@
                     signer = Signature.getInstance(mSignatureAlgorithm, "BC");
                     break;
                 default:
-                    throw new RuntimeException(mImplementation.toString());
+                    throw new RuntimeException(implementation.toString());
             }
             signer.initSign(mPrivateKey);
             signer.update(DATA);
@@ -142,11 +136,13 @@
     }
 
     @Test
-    public void timeVerify() throws Exception {
+    @Parameters(method = "getData")
+    public void timeVerify(Algorithm algorithm, Implementation implementation) throws Exception {
+        setUp(algorithm);
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
             Signature verifier;
-            switch (mImplementation) {
+            switch (implementation) {
                 case OpenSSL:
                     verifier = Signature.getInstance(mSignatureAlgorithm, "AndroidOpenSSL");
                     break;
@@ -154,7 +150,7 @@
                     verifier = Signature.getInstance(mSignatureAlgorithm, "BC");
                     break;
                 default:
-                    throw new RuntimeException(mImplementation.toString());
+                    throw new RuntimeException(implementation.toString());
             }
             verifier.initVerify(mPublicKey);
             verifier.update(DATA);
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/StringPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/StringPerfTest.java
index 02194b1..36db014 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/StringPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/StringPerfTest.java
@@ -20,16 +20,17 @@
 import android.perftests.utils.PerfStatusReporter;
 import android.test.suitebuilder.annotation.LargeTest;
 
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
 
 import java.util.Arrays;
 import java.util.Collection;
 
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
 @LargeTest
 public class StringPerfTest {
     @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
@@ -46,8 +47,7 @@
         }
     }
 
-    @Parameters(name = "mStringLengths={0}")
-    public static Collection<Object[]> data() {
+    public static Collection<Object[]> getData() {
         return Arrays.asList(
                 new Object[][] {
                     {StringLengths.EIGHT_KI},
@@ -57,9 +57,6 @@
                 });
     }
 
-    @Parameterized.Parameter(0)
-    public StringLengths mStringLengths;
-
     private static String makeString(int length) {
         StringBuilder result = new StringBuilder(length);
         for (int i = 0; i < length; ++i) {
@@ -69,10 +66,11 @@
     }
 
     @Test
-    public void timeHashCode() {
+    @Parameters(method = "getData")
+    public void timeHashCode(StringLengths stringLengths) {
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            mStringLengths.mValue.hashCode();
+            stringLengths.mValue.hashCode();
         }
     }
 }
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/StringReplaceAllPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/StringReplaceAllPerfTest.java
index b0d1ee4..5b4423a 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/StringReplaceAllPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/StringReplaceAllPerfTest.java
@@ -20,16 +20,17 @@
 import android.perftests.utils.PerfStatusReporter;
 import android.test.suitebuilder.annotation.LargeTest;
 
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
 
 import java.util.Arrays;
 import java.util.Collection;
 
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
 @LargeTest
 public class StringReplaceAllPerfTest {
     @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
@@ -69,8 +70,7 @@
         return stringBuilder.toString();
     }
 
-    @Parameters(name = "mStringLengths={0}")
-    public static Collection<Object[]> data() {
+    public static Collection<Object[]> getData() {
         return Arrays.asList(
                 new Object[][] {
                     {StringLengths.BOOT_IMAGE},
@@ -82,30 +82,30 @@
                 });
     }
 
-    @Parameterized.Parameter(0)
-    public StringLengths mStringLengths;
-
     @Test
-    public void timeReplaceAllTrivialPatternNonExistent() {
+    @Parameters(method = "getData")
+    public void timeReplaceAllTrivialPatternNonExistent(StringLengths stringLengths) {
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            mStringLengths.mValue.replaceAll("fish", "0");
+            stringLengths.mValue.replaceAll("fish", "0");
         }
     }
 
     @Test
-    public void timeReplaceTrivialPatternAllRepeated() {
+    @Parameters(method = "getData")
+    public void timeReplaceTrivialPatternAllRepeated(StringLengths stringLengths) {
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            mStringLengths.mValue.replaceAll("jklm", "0");
+            stringLengths.mValue.replaceAll("jklm", "0");
         }
     }
 
     @Test
-    public void timeReplaceAllTrivialPatternSingleOccurrence() {
+    @Parameters(method = "getData")
+    public void timeReplaceAllTrivialPatternSingleOccurrence(StringLengths stringLengths) {
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            mStringLengths.mValue.replaceAll("qrst", "0");
+            stringLengths.mValue.replaceAll("qrst", "0");
         }
     }
 }
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/StringReplacePerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/StringReplacePerfTest.java
index d2e657a..4d5c792 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/StringReplacePerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/StringReplacePerfTest.java
@@ -20,16 +20,17 @@
 import android.perftests.utils.PerfStatusReporter;
 import android.test.suitebuilder.annotation.LargeTest;
 
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
 
 import java.util.Arrays;
 import java.util.Collection;
 
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
 @LargeTest
 public class StringReplacePerfTest {
     @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
@@ -64,8 +65,7 @@
         return stringBuilder.toString();
     }
 
-    @Parameters(name = "mStringLengths={0}")
-    public static Collection<Object[]> data() {
+    public static Collection<Object[]> getData() {
         return Arrays.asList(
                 new Object[][] {
                     {StringLengths.EMPTY},
@@ -76,54 +76,57 @@
                 });
     }
 
-    @Parameterized.Parameter(0)
-    public StringLengths mStringLengths;
-
     @Test
-    public void timeReplaceCharNonExistent() {
+    @Parameters(method = "getData")
+    public void timeReplaceCharNonExistent(StringLengths stringLengths) {
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            mStringLengths.mValue.replace('z', '0');
+            stringLengths.mValue.replace('z', '0');
         }
     }
 
     @Test
-    public void timeReplaceCharRepeated() {
+    @Parameters(method = "getData")
+    public void timeReplaceCharRepeated(StringLengths stringLengths) {
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            mStringLengths.mValue.replace('a', '0');
+            stringLengths.mValue.replace('a', '0');
         }
     }
 
     @Test
-    public void timeReplaceSingleChar() {
+    @Parameters(method = "getData")
+    public void timeReplaceSingleChar(StringLengths stringLengths) {
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            mStringLengths.mValue.replace('q', '0');
+            stringLengths.mValue.replace('q', '0');
         }
     }
 
     @Test
-    public void timeReplaceSequenceNonExistent() {
+    @Parameters(method = "getData")
+    public void timeReplaceSequenceNonExistent(StringLengths stringLengths) {
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            mStringLengths.mValue.replace("fish", "0");
+            stringLengths.mValue.replace("fish", "0");
         }
     }
 
     @Test
-    public void timeReplaceSequenceRepeated() {
+    @Parameters(method = "getData")
+    public void timeReplaceSequenceRepeated(StringLengths stringLengths) {
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            mStringLengths.mValue.replace("jklm", "0");
+            stringLengths.mValue.replace("jklm", "0");
         }
     }
 
     @Test
-    public void timeReplaceSingleSequence() {
+    @Parameters(method = "getData")
+    public void timeReplaceSingleSequence(StringLengths stringLengths) {
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            mStringLengths.mValue.replace("qrst", "0");
+            stringLengths.mValue.replace("qrst", "0");
         }
     }
 }
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/StringToBytesPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/StringToBytesPerfTest.java
index 1efc188..c004d95 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/StringToBytesPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/StringToBytesPerfTest.java
@@ -20,17 +20,18 @@
 import android.perftests.utils.PerfStatusReporter;
 import android.test.suitebuilder.annotation.LargeTest;
 
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
 
 import java.nio.charset.StandardCharsets;
 import java.util.Arrays;
 import java.util.Collection;
 
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
 @LargeTest
 public class StringToBytesPerfTest {
     @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
@@ -53,8 +54,7 @@
         }
     }
 
-    @Parameters(name = "mStringLengths={0}")
-    public static Collection<Object[]> data() {
+    public static Collection<Object[]> getData() {
         return Arrays.asList(
                 new Object[][] {
                     {StringLengths.EMPTY},
@@ -69,9 +69,6 @@
                 });
     }
 
-    @Parameterized.Parameter(0)
-    public StringLengths mStringLengths;
-
     private static String makeString(int length) {
         char[] chars = new char[length];
         for (int i = 0; i < length; ++i) {
@@ -89,26 +86,29 @@
     }
 
     @Test
-    public void timeGetBytesUtf8() {
+    @Parameters(method = "getData")
+    public void timeGetBytesUtf8(StringLengths stringLengths) {
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            mStringLengths.mValue.getBytes(StandardCharsets.UTF_8);
+            stringLengths.mValue.getBytes(StandardCharsets.UTF_8);
         }
     }
 
     @Test
-    public void timeGetBytesIso88591() {
+    @Parameters(method = "getData")
+    public void timeGetBytesIso88591(StringLengths stringLengths) {
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            mStringLengths.mValue.getBytes(StandardCharsets.ISO_8859_1);
+            stringLengths.mValue.getBytes(StandardCharsets.ISO_8859_1);
         }
     }
 
     @Test
-    public void timeGetBytesAscii() {
+    @Parameters(method = "getData")
+    public void timeGetBytesAscii(StringLengths stringLengths) {
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            mStringLengths.mValue.getBytes(StandardCharsets.US_ASCII);
+            stringLengths.mValue.getBytes(StandardCharsets.US_ASCII);
         }
     }
 }
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/StringToRealPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/StringToRealPerfTest.java
index b01948a..15516fc 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/StringToRealPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/StringToRealPerfTest.java
@@ -20,22 +20,22 @@
 import android.perftests.utils.PerfStatusReporter;
 import android.test.suitebuilder.annotation.LargeTest;
 
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
 
 import java.util.Arrays;
 import java.util.Collection;
 
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
 @LargeTest
 public class StringToRealPerfTest {
     @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
 
-    @Parameters(name = "mString={0}")
-    public static Collection<Object[]> data() {
+    public static Collection<Object[]> getData() {
         return Arrays.asList(
                 new Object[][] {
                     {"NaN"},
@@ -49,22 +49,21 @@
                 });
     }
 
-    @Parameterized.Parameter(0)
-    public String mString;
-
     @Test
-    public void timeFloat_parseFloat() {
+    @Parameters(method = "getData")
+    public void timeFloat_parseFloat(String string) {
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            Float.parseFloat(mString);
+            Float.parseFloat(string);
         }
     }
 
     @Test
-    public void timeDouble_parseDouble() {
+    @Parameters(method = "getData")
+    public void timeDouble_parseDouble(String string) {
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
-            Double.parseDouble(mString);
+            Double.parseDouble(string);
         }
     }
 }
diff --git a/apct-tests/perftests/core/src/android/libcore/regression/XMLEntitiesPerfTest.java b/apct-tests/perftests/core/src/android/libcore/regression/XMLEntitiesPerfTest.java
index 2ea834d..ae1e8bc 100644
--- a/apct-tests/perftests/core/src/android/libcore/regression/XMLEntitiesPerfTest.java
+++ b/apct-tests/perftests/core/src/android/libcore/regression/XMLEntitiesPerfTest.java
@@ -20,12 +20,12 @@
 import android.perftests.utils.PerfStatusReporter;
 import android.test.suitebuilder.annotation.LargeTest;
 
-import org.junit.Before;
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.junit.runners.Parameterized;
-import org.junit.runners.Parameterized.Parameters;
 import org.xml.sax.InputSource;
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserFactory;
@@ -38,13 +38,12 @@
 import javax.xml.parsers.DocumentBuilderFactory;
 
 // http://code.google.com/p/android/issues/detail?id=18102
-@RunWith(Parameterized.class)
+@RunWith(JUnitParamsRunner.class)
 @LargeTest
 public final class XMLEntitiesPerfTest {
     @Rule public PerfStatusReporter mPerfStatusReporter = new PerfStatusReporter();
 
-    @Parameters(name = "mLength={0}, mEntityFraction={1}")
-    public static Collection<Object[]> data() {
+    public static Collection<Object[]> getData() {
         return Arrays.asList(
                 new Object[][] {
                     {10, 0},
@@ -59,29 +58,22 @@
                 });
     }
 
-    @Parameterized.Parameter(0)
-    public int mLength;
-
-    @Parameterized.Parameter(1)
-    public float mEntityFraction;
-
     private XmlPullParserFactory mXmlPullParserFactory;
     private DocumentBuilderFactory mDocumentBuilderFactory;
 
     /** a string like {@code <doc>&amp;&amp;++</doc>}. */
     private String mXml;
 
-    @Before
-    public void setUp() throws Exception {
+    public void setUp(int length, float entityFraction) throws Exception {
         mXmlPullParserFactory = XmlPullParserFactory.newInstance();
         mDocumentBuilderFactory = DocumentBuilderFactory.newInstance();
 
         StringBuilder xmlBuilder = new StringBuilder();
         xmlBuilder.append("<doc>");
-        for (int i = 0; i < (mLength * mEntityFraction); i++) {
+        for (int i = 0; i < (length * entityFraction); i++) {
             xmlBuilder.append("&amp;");
         }
-        while (xmlBuilder.length() < mLength) {
+        while (xmlBuilder.length() < length) {
             xmlBuilder.append("+");
         }
         xmlBuilder.append("</doc>");
@@ -89,7 +81,9 @@
     }
 
     @Test
-    public void timeXmlParser() throws Exception {
+    @Parameters(method = "getData")
+    public void timeXmlParser(int length, float entityFraction) throws Exception {
+        setUp(length, entityFraction);
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
             XmlPullParser parser = mXmlPullParserFactory.newPullParser();
@@ -101,7 +95,9 @@
     }
 
     @Test
-    public void timeDocumentBuilder() throws Exception {
+    @Parameters(method = "getData")
+    public void timeDocumentBuilder(int length, float entityFraction) throws Exception {
+        setUp(length, entityFraction);
         BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         while (state.keepRunning()) {
             DocumentBuilder documentBuilder = mDocumentBuilderFactory.newDocumentBuilder();
diff --git a/apex/jobscheduler/framework/java/android/app/AlarmManager.java b/apex/jobscheduler/framework/java/android/app/AlarmManager.java
index 5a445d4..dade7c3 100644
--- a/apex/jobscheduler/framework/java/android/app/AlarmManager.java
+++ b/apex/jobscheduler/framework/java/android/app/AlarmManager.java
@@ -309,7 +309,7 @@
         /**
          * Callback method that is invoked by the system when the alarm time is reached.
          */
-        public void onAlarm();
+        void onAlarm();
     }
 
     final class ListenerWrapper extends IAlarmListener.Stub implements Runnable {
@@ -453,7 +453,7 @@
      * @see #RTC
      * @see #RTC_WAKEUP
      */
-    public void set(@AlarmType int type, long triggerAtMillis, PendingIntent operation) {
+    public void set(@AlarmType int type, long triggerAtMillis, @NonNull PendingIntent operation) {
         setImpl(type, triggerAtMillis, legacyExactLength(), 0, 0, operation, null, null,
                 (Handler) null, null, null);
     }
@@ -480,8 +480,8 @@
      * @param targetHandler {@link Handler} on which to execute the listener's onAlarm()
      *         callback, or {@code null} to run that callback on the main looper.
      */
-    public void set(@AlarmType int type, long triggerAtMillis, String tag, OnAlarmListener listener,
-            Handler targetHandler) {
+    public void set(@AlarmType int type, long triggerAtMillis, @Nullable String tag,
+            @NonNull OnAlarmListener listener, @Nullable Handler targetHandler) {
         setImpl(type, triggerAtMillis, legacyExactLength(), 0, 0, null, listener, tag,
                 targetHandler, null, null);
     }
@@ -546,7 +546,7 @@
      * @see Intent#EXTRA_ALARM_COUNT
      */
     public void setRepeating(@AlarmType int type, long triggerAtMillis,
-            long intervalMillis, PendingIntent operation) {
+            long intervalMillis, @NonNull PendingIntent operation) {
         setImpl(type, triggerAtMillis, legacyExactLength(), intervalMillis, 0, operation,
                 null, null, (Handler) null, null, null);
     }
@@ -602,7 +602,7 @@
      * @see #RTC_WAKEUP
      */
     public void setWindow(@AlarmType int type, long windowStartMillis, long windowLengthMillis,
-            PendingIntent operation) {
+            @NonNull PendingIntent operation) {
         setImpl(type, windowStartMillis, windowLengthMillis, 0, 0, operation,
                 null, null, (Handler) null, null, null);
     }
@@ -625,12 +625,62 @@
      * @see #setWindow(int, long, long, PendingIntent)
      */
     public void setWindow(@AlarmType int type, long windowStartMillis, long windowLengthMillis,
-            String tag, OnAlarmListener listener, Handler targetHandler) {
+            @Nullable String tag, @NonNull OnAlarmListener listener,
+            @Nullable Handler targetHandler) {
         setImpl(type, windowStartMillis, windowLengthMillis, 0, 0, null, listener, tag,
                 targetHandler, null, null);
     }
 
     /**
+     * Direct callback version of {@link #setWindow(int, long, long, PendingIntent)}.  Rather
+     * than supplying a PendingIntent to be sent when the alarm time is reached, this variant
+     * supplies an {@link OnAlarmListener} instance that will be invoked at that time.
+     * <p>
+     * The OnAlarmListener {@link OnAlarmListener#onAlarm() onAlarm()} method will be
+     * invoked via the specified target Executor.
+     *
+     * <p>
+     * Note: Starting with API {@link Build.VERSION_CODES#S}, apps should not pass in a window of
+     * less than 10 minutes. The system will try its best to accommodate smaller windows if the
+     * alarm is supposed to fire in the near future, but there are no guarantees and the app should
+     * expect any window smaller than 10 minutes to get elongated to 10 minutes.
+     *
+     * @see #setWindow(int, long, long, PendingIntent)
+     */
+    public void setWindow(@AlarmType int type, long windowStartMillis, long windowLengthMillis,
+            @Nullable String tag, @NonNull Executor executor, @NonNull OnAlarmListener listener) {
+        setImpl(type, windowStartMillis, windowLengthMillis, 0, 0, null, listener, tag,
+                executor, null, null);
+    }
+
+    /**
+     * Direct callback version of {@link #setWindow(int, long, long, PendingIntent)}.  Rather
+     * than supplying a PendingIntent to be sent when the alarm time is reached, this variant
+     * supplies an {@link OnAlarmListener} instance that will be invoked at that time.
+     * <p>
+     * The OnAlarmListener {@link OnAlarmListener#onAlarm() onAlarm()} method will be
+     * invoked via the specified target Executor.
+     *
+     * <p>
+     * Note: Starting with API {@link Build.VERSION_CODES#S}, apps should not pass in a window of
+     * less than 10 minutes. The system will try its best to accommodate smaller windows if the
+     * alarm is supposed to fire in the near future, but there are no guarantees and the app should
+     * expect any window smaller than 10 minutes to get elongated to 10 minutes.
+     *
+     * @see #setWindow(int, long, long, PendingIntent)
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS)
+    public void setWindow(@AlarmType int type, long windowStartMillis, long windowLengthMillis,
+            @Nullable String tag, @NonNull Executor executor, @Nullable WorkSource workSource,
+            @NonNull OnAlarmListener listener) {
+        setImpl(type, windowStartMillis, windowLengthMillis, 0, 0, null, listener, tag,
+                executor, workSource, null);
+    }
+
+    /**
      * Schedule an alarm that is prioritized by the system while the device is in power saving modes
      * such as battery saver and device idle (doze).
      *
@@ -725,7 +775,8 @@
      * @see Manifest.permission#SCHEDULE_EXACT_ALARM SCHEDULE_EXACT_ALARM
      */
     @RequiresPermission(value = Manifest.permission.SCHEDULE_EXACT_ALARM, conditional = true)
-    public void setExact(@AlarmType int type, long triggerAtMillis, PendingIntent operation) {
+    public void setExact(@AlarmType int type, long triggerAtMillis,
+            @NonNull PendingIntent operation) {
         setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, 0, operation, null, null, (Handler) null,
                 null, null);
     }
@@ -756,8 +807,8 @@
      * @see Manifest.permission#SCHEDULE_EXACT_ALARM SCHEDULE_EXACT_ALARM
      */
     @RequiresPermission(value = Manifest.permission.SCHEDULE_EXACT_ALARM, conditional = true)
-    public void setExact(@AlarmType int type, long triggerAtMillis, String tag,
-            OnAlarmListener listener, Handler targetHandler) {
+    public void setExact(@AlarmType int type, long triggerAtMillis, @Nullable String tag,
+            @NonNull OnAlarmListener listener, @Nullable Handler targetHandler) {
         setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, 0, null, listener, tag,
                 targetHandler, null, null);
     }
@@ -767,8 +818,8 @@
      * the given time.
      * @hide
      */
-    public void setIdleUntil(@AlarmType int type, long triggerAtMillis, String tag,
-            OnAlarmListener listener, Handler targetHandler) {
+    public void setIdleUntil(@AlarmType int type, long triggerAtMillis, @Nullable String tag,
+            @NonNull OnAlarmListener listener, @Nullable Handler targetHandler) {
         setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, FLAG_IDLE_UNTIL, null,
                 listener, tag, targetHandler, null, null);
     }
@@ -828,7 +879,7 @@
      * @see Manifest.permission#SCHEDULE_EXACT_ALARM SCHEDULE_EXACT_ALARM
      */
     @RequiresPermission(Manifest.permission.SCHEDULE_EXACT_ALARM)
-    public void setAlarmClock(AlarmClockInfo info, PendingIntent operation) {
+    public void setAlarmClock(@NonNull AlarmClockInfo info, @NonNull PendingIntent operation) {
         setImpl(RTC_WAKEUP, info.getTriggerTime(), WINDOW_EXACT, 0, 0, operation,
                 null, null, (Handler) null, null, info);
     }
@@ -837,7 +888,8 @@
     @SystemApi
     @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS)
     public void set(@AlarmType int type, long triggerAtMillis, long windowMillis,
-            long intervalMillis, PendingIntent operation, WorkSource workSource) {
+            long intervalMillis, @NonNull PendingIntent operation,
+            @Nullable WorkSource workSource) {
         setImpl(type, triggerAtMillis, windowMillis, intervalMillis, 0, operation, null, null,
                 (Handler) null, workSource, null);
     }
@@ -854,8 +906,8 @@
      */
     @UnsupportedAppUsage
     public void set(@AlarmType int type, long triggerAtMillis, long windowMillis,
-            long intervalMillis, String tag, OnAlarmListener listener, Handler targetHandler,
-            WorkSource workSource) {
+            long intervalMillis, @Nullable String tag, @NonNull OnAlarmListener listener,
+            @Nullable Handler targetHandler, @Nullable WorkSource workSource) {
         setImpl(type, triggerAtMillis, windowMillis, intervalMillis, 0, null, listener, tag,
                 targetHandler, workSource, null);
     }
@@ -873,8 +925,8 @@
     @SystemApi
     @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS)
     public void set(@AlarmType int type, long triggerAtMillis, long windowMillis,
-            long intervalMillis, OnAlarmListener listener, Handler targetHandler,
-            WorkSource workSource) {
+            long intervalMillis, @NonNull OnAlarmListener listener, @Nullable Handler targetHandler,
+            @Nullable WorkSource workSource) {
         setImpl(type, triggerAtMillis, windowMillis, intervalMillis, 0, null, listener, null,
                 targetHandler, workSource, null);
     }
@@ -1072,7 +1124,7 @@
      * @see Intent#EXTRA_ALARM_COUNT
      */
     public void setInexactRepeating(@AlarmType int type, long triggerAtMillis,
-            long intervalMillis, PendingIntent operation) {
+            long intervalMillis, @NonNull PendingIntent operation) {
         setImpl(type, triggerAtMillis, WINDOW_HEURISTIC, intervalMillis, 0, operation, null,
                 null, (Handler) null, null, null);
     }
@@ -1122,7 +1174,7 @@
      * @see #RTC_WAKEUP
      */
     public void setAndAllowWhileIdle(@AlarmType int type, long triggerAtMillis,
-            PendingIntent operation) {
+            @NonNull PendingIntent operation) {
         setImpl(type, triggerAtMillis, WINDOW_HEURISTIC, 0, FLAG_ALLOW_WHILE_IDLE,
                 operation, null, null, (Handler) null, null, null);
     }
@@ -1195,12 +1247,46 @@
      */
     @RequiresPermission(value = Manifest.permission.SCHEDULE_EXACT_ALARM, conditional = true)
     public void setExactAndAllowWhileIdle(@AlarmType int type, long triggerAtMillis,
-            PendingIntent operation) {
+            @NonNull PendingIntent operation) {
         setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, FLAG_ALLOW_WHILE_IDLE, operation,
                 null, null, (Handler) null, null, null);
     }
 
     /**
+     * Like {@link #setExact(int, long, String, Executor, WorkSource, OnAlarmListener)}, but this
+     * alarm will be allowed to execute even when the system is in low-power idle modes.
+     *
+     * <p> See {@link #setExactAndAllowWhileIdle(int, long, PendingIntent)} for more details.
+     *
+     * @param type            type of alarm
+     * @param triggerAtMillis The exact time in milliseconds, that the alarm should be delivered,
+     *                        expressed in the appropriate clock's units (depending on the alarm
+     *                        type).
+     * @param listener        {@link OnAlarmListener} instance whose
+     *                        {@link OnAlarmListener#onAlarm() onAlarm()} method will be called when
+     *                        the alarm time is reached.
+     * @param executor        The {@link Executor} on which to execute the listener's onAlarm()
+     *                        callback.
+     * @param tag             Optional. A string tag used to identify this alarm in logs and
+     *                        battery-attribution.
+     * @param workSource      A {@link WorkSource} object to attribute this alarm to the app that
+     *                        requested this work.
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(allOf = {
+            Manifest.permission.UPDATE_DEVICE_STATS,
+            Manifest.permission.SCHEDULE_EXACT_ALARM}, conditional = true)
+    public void setExactAndAllowWhileIdle(@AlarmType int type, long triggerAtMillis,
+            @Nullable String tag, @NonNull Executor executor, @Nullable WorkSource workSource,
+            @NonNull OnAlarmListener listener) {
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(listener);
+        setImpl(type, triggerAtMillis, WINDOW_EXACT, 0, FLAG_ALLOW_WHILE_IDLE, null, listener, tag,
+                executor, workSource, null);
+    }
+
+    /**
      * Remove any alarms with a matching {@link Intent}.
      * Any alarm, of any type, whose Intent matches this one (as defined by
      * {@link Intent#filterEquals}), will be canceled.
@@ -1210,7 +1296,7 @@
      *
      * @see #set
      */
-    public void cancel(PendingIntent operation) {
+    public void cancel(@NonNull PendingIntent operation) {
         if (operation == null) {
             final String msg = "cancel() called with a null PendingIntent";
             if (mTargetSdkVersion >= Build.VERSION_CODES.N) {
@@ -1233,7 +1319,7 @@
      *
      * @param listener OnAlarmListener instance that is the target of a currently-set alarm.
      */
-    public void cancel(OnAlarmListener listener) {
+    public void cancel(@NonNull OnAlarmListener listener) {
         if (listener == null) {
             throw new NullPointerException("cancel() called with a null OnAlarmListener");
         }
diff --git a/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java b/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java
index 299ad66..4a3a6d9 100644
--- a/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java
+++ b/apex/jobscheduler/framework/java/android/app/tare/EconomyManager.java
@@ -113,7 +113,9 @@
     /** @hide */
     public static final String KEY_AM_INITIAL_CONSUMPTION_LIMIT = "am_initial_consumption_limit";
     /** @hide */
-    public static final String KEY_AM_HARD_CONSUMPTION_LIMIT = "am_hard_consumption_limit";
+    public static final String KEY_AM_MIN_CONSUMPTION_LIMIT = "am_minimum_consumption_limit";
+    /** @hide */
+    public static final String KEY_AM_MAX_CONSUMPTION_LIMIT = "am_maximum_consumption_limit";
     // TODO: Add AlarmManager modifier keys
     /** @hide */
     public static final String KEY_AM_REWARD_TOP_ACTIVITY_INSTANT =
@@ -242,7 +244,9 @@
     /** @hide */
     public static final String KEY_JS_INITIAL_CONSUMPTION_LIMIT = "js_initial_consumption_limit";
     /** @hide */
-    public static final String KEY_JS_HARD_CONSUMPTION_LIMIT = "js_hard_consumption_limit";
+    public static final String KEY_JS_MIN_CONSUMPTION_LIMIT = "js_minimum_consumption_limit";
+    /** @hide */
+    public static final String KEY_JS_MAX_CONSUMPTION_LIMIT = "js_maximum_consumption_limit";
     // TODO: Add JobScheduler modifier keys
     /** @hide */
     public static final String KEY_JS_REWARD_APP_INSTALL_INSTANT =
@@ -371,7 +375,9 @@
     /** @hide */
     public static final long DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT_CAKES = arcToCake(2880);
     /** @hide */
-    public static final long DEFAULT_AM_HARD_CONSUMPTION_LIMIT_CAKES = arcToCake(15_000);
+    public static final long DEFAULT_AM_MIN_CONSUMPTION_LIMIT_CAKES = arcToCake(1440);
+    /** @hide */
+    public static final long DEFAULT_AM_MAX_CONSUMPTION_LIMIT_CAKES = arcToCake(15_000);
     // TODO: add AlarmManager modifier default values
     /** @hide */
     public static final long DEFAULT_AM_REWARD_TOP_ACTIVITY_INSTANT_CAKES = arcToCake(0);
@@ -478,8 +484,10 @@
     /** @hide */
     public static final long DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT_CAKES = arcToCake(29_000);
     /** @hide */
-    // TODO: set hard limit based on device type (phone vs tablet vs etc) + battery size
-    public static final long DEFAULT_JS_HARD_CONSUMPTION_LIMIT_CAKES = arcToCake(250_000);
+    public static final long DEFAULT_JS_MIN_CONSUMPTION_LIMIT_CAKES = arcToCake(17_000);
+    /** @hide */
+    // TODO: set maximum limit based on device type (phone vs tablet vs etc) + battery size
+    public static final long DEFAULT_JS_MAX_CONSUMPTION_LIMIT_CAKES = arcToCake(250_000);
     // TODO: add JobScheduler modifier default values
     /** @hide */
     public static final long DEFAULT_JS_REWARD_APP_INSTALL_INSTANT_CAKES = arcToCake(408);
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
index 20bca35..52dc01b 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobConcurrencyManager.java
@@ -460,14 +460,14 @@
                     if (mPowerManager != null && mPowerManager.isDeviceIdleMode()) {
                         synchronized (mLock) {
                             stopUnexemptedJobsForDoze();
-                            stopLongRunningJobsLocked("deep doze");
+                            stopOvertimeJobsLocked("deep doze");
                         }
                     }
                     break;
                 case PowerManager.ACTION_POWER_SAVE_MODE_CHANGED:
                     if (mPowerManager != null && mPowerManager.isPowerSaveMode()) {
                         synchronized (mLock) {
-                            stopLongRunningJobsLocked("battery saver");
+                            stopOvertimeJobsLocked("battery saver");
                         }
                     }
                     break;
@@ -555,7 +555,7 @@
      * execution guarantee.
      */
     @GuardedBy("mLock")
-    boolean isJobLongRunningLocked(@NonNull JobStatus job) {
+    boolean isJobInOvertimeLocked(@NonNull JobStatus job) {
         if (!mRunningJobs.contains(job)) {
             return false;
         }
@@ -1043,7 +1043,7 @@
     }
 
     @GuardedBy("mLock")
-    private void stopLongRunningJobsLocked(@NonNull String debugReason) {
+    private void stopOvertimeJobsLocked(@NonNull String debugReason) {
         for (int i = 0; i < mActiveServices.size(); ++i) {
             final JobServiceContext jsc = mActiveServices.get(i);
             final JobStatus jobStatus = jsc.getRunningJobLocked();
@@ -1060,7 +1060,7 @@
      * restricted by the given {@link JobRestriction}.
      */
     @GuardedBy("mLock")
-    void maybeStopLongRunningJobsLocked(@NonNull JobRestriction restriction) {
+    void maybeStopOvertimeJobsLocked(@NonNull JobRestriction restriction) {
         for (int i = mActiveServices.size() - 1; i >= 0; --i) {
             final JobServiceContext jsc = mActiveServices.get(i);
             final JobStatus jobStatus = jsc.getRunningJobLocked();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index d28ebde..d9fb318 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -432,6 +432,7 @@
                             break;
                         case Constants.KEY_MIN_LINEAR_BACKOFF_TIME_MS:
                         case Constants.KEY_MIN_EXP_BACKOFF_TIME_MS:
+                        case Constants.KEY_SYSTEM_STOP_TO_FAILURE_RATIO:
                             mConstants.updateBackoffConstantsLocked();
                             break;
                         case Constants.KEY_CONN_CONGESTION_DELAY_FRAC:
@@ -509,6 +510,8 @@
 
         private static final String KEY_MIN_LINEAR_BACKOFF_TIME_MS = "min_linear_backoff_time_ms";
         private static final String KEY_MIN_EXP_BACKOFF_TIME_MS = "min_exp_backoff_time_ms";
+        private static final String KEY_SYSTEM_STOP_TO_FAILURE_RATIO =
+                "system_stop_to_failure_ratio";
         private static final String KEY_CONN_CONGESTION_DELAY_FRAC = "conn_congestion_delay_frac";
         private static final String KEY_CONN_PREFETCH_RELAX_FRAC = "conn_prefetch_relax_frac";
         private static final String KEY_CONN_USE_CELL_SIGNAL_STRENGTH =
@@ -540,6 +543,7 @@
         private static final float DEFAULT_MODERATE_USE_FACTOR = .5f;
         private static final long DEFAULT_MIN_LINEAR_BACKOFF_TIME_MS = JobInfo.MIN_BACKOFF_MILLIS;
         private static final long DEFAULT_MIN_EXP_BACKOFF_TIME_MS = JobInfo.MIN_BACKOFF_MILLIS;
+        private static final int DEFAULT_SYSTEM_STOP_TO_FAILURE_RATIO = 3;
         private static final float DEFAULT_CONN_CONGESTION_DELAY_FRAC = 0.5f;
         private static final float DEFAULT_CONN_PREFETCH_RELAX_FRAC = 0.5f;
         private static final boolean DEFAULT_CONN_USE_CELL_SIGNAL_STRENGTH = true;
@@ -589,6 +593,11 @@
          * The minimum backoff time to allow for exponential backoff.
          */
         long MIN_EXP_BACKOFF_TIME_MS = DEFAULT_MIN_EXP_BACKOFF_TIME_MS;
+        /**
+         * The ratio to use to convert number of times a job was stopped by JobScheduler to an
+         * incremental failure in the backoff policy calculation.
+         */
+        int SYSTEM_STOP_TO_FAILURE_RATIO = DEFAULT_SYSTEM_STOP_TO_FAILURE_RATIO;
 
         /**
          * The fraction of a job's running window that must pass before we
@@ -700,6 +709,9 @@
             MIN_EXP_BACKOFF_TIME_MS = DeviceConfig.getLong(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
                     KEY_MIN_EXP_BACKOFF_TIME_MS,
                     DEFAULT_MIN_EXP_BACKOFF_TIME_MS);
+            SYSTEM_STOP_TO_FAILURE_RATIO = DeviceConfig.getInt(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
+                    KEY_SYSTEM_STOP_TO_FAILURE_RATIO,
+                    DEFAULT_SYSTEM_STOP_TO_FAILURE_RATIO);
         }
 
         private void updateConnectivityConstantsLocked() {
@@ -797,6 +809,7 @@
 
             pw.print(KEY_MIN_LINEAR_BACKOFF_TIME_MS, MIN_LINEAR_BACKOFF_TIME_MS).println();
             pw.print(KEY_MIN_EXP_BACKOFF_TIME_MS, MIN_EXP_BACKOFF_TIME_MS).println();
+            pw.print(KEY_SYSTEM_STOP_TO_FAILURE_RATIO, SYSTEM_STOP_TO_FAILURE_RATIO).println();
             pw.print(KEY_CONN_CONGESTION_DELAY_FRAC, CONN_CONGESTION_DELAY_FRAC).println();
             pw.print(KEY_CONN_PREFETCH_RELAX_FRAC, CONN_PREFETCH_RELAX_FRAC).println();
             pw.print(KEY_CONN_USE_CELL_SIGNAL_STRENGTH, CONN_USE_CELL_SIGNAL_STRENGTH).println();
@@ -1277,7 +1290,7 @@
                     jobStatus.getJob().isPrefetch(),
                     jobStatus.getJob().getPriority(),
                     jobStatus.getEffectivePriority(),
-                    jobStatus.getNumFailures());
+                    jobStatus.getNumPreviousAttempts());
 
             // If the job is immediately ready to run, then we can just immediately
             // put it in the pending list and try to schedule it.  This is especially
@@ -1476,7 +1489,7 @@
                     cancelled.getJob().isPrefetch(),
                     cancelled.getJob().getPriority(),
                     cancelled.getEffectivePriority(),
-                    cancelled.getNumFailures());
+                    cancelled.getNumPreviousAttempts());
         }
         // If this is a replacement, bring in the new version of the job
         if (incomingJob != null) {
@@ -1820,12 +1833,13 @@
             Slog.wtf(TAG, "Not yet prepared when started tracking: " + jobStatus);
         }
         jobStatus.enqueueTime = sElapsedRealtimeClock.millis();
-        final boolean update = mJobs.add(jobStatus);
+        final boolean update = lastJob != null;
+        mJobs.add(jobStatus);
         if (mReadyToRock) {
             for (int i = 0; i < mControllers.size(); i++) {
                 StateController controller = mControllers.get(i);
                 if (update) {
-                    controller.maybeStopTrackingJobLocked(jobStatus, null, true);
+                    controller.maybeStopTrackingJobLocked(jobStatus, null);
                 }
                 controller.maybeStartTrackingJobLocked(jobStatus, lastJob);
             }
@@ -1858,7 +1872,7 @@
         if (mReadyToRock) {
             for (int i = 0; i < mControllers.size(); i++) {
                 StateController controller = mControllers.get(i);
-                controller.maybeStopTrackingJobLocked(jobStatus, incomingJob, false);
+                controller.maybeStopTrackingJobLocked(jobStatus, incomingJob);
             }
         }
         return removed;
@@ -1870,10 +1884,10 @@
         return mConcurrencyManager.isJobRunningLocked(job);
     }
 
-    /** @see JobConcurrencyManager#isJobLongRunningLocked(JobStatus) */
+    /** @see JobConcurrencyManager#isJobInOvertimeLocked(JobStatus) */
     @GuardedBy("mLock")
-    public boolean isLongRunningLocked(JobStatus job) {
-        return mConcurrencyManager.isJobLongRunningLocked(job);
+    public boolean isJobInOvertimeLocked(JobStatus job) {
+        return mConcurrencyManager.isJobInOvertimeLocked(job);
     }
 
     private void noteJobPending(JobStatus job) {
@@ -1903,7 +1917,7 @@
      * Reschedules the given job based on the job's backoff policy. It doesn't make sense to
      * specify an override deadline on a failed job (the failed job will run even though it's not
      * ready), so we reschedule it with {@link JobStatus#NO_LATEST_RUNTIME}, but specify that any
-     * ready job with {@link JobStatus#getNumFailures()} > 0 will be executed.
+     * ready job with {@link JobStatus#getNumPreviousAttempts()} > 0 will be executed.
      *
      * @param failureToReschedule Provided job status that we will reschedule.
      * @return A newly instantiated JobStatus with the same constraints as the last job except
@@ -1911,12 +1925,24 @@
      * @see #maybeQueueReadyJobsForExecutionLocked
      */
     @VisibleForTesting
-    JobStatus getRescheduleJobForFailureLocked(JobStatus failureToReschedule) {
+    JobStatus getRescheduleJobForFailureLocked(JobStatus failureToReschedule,
+            int internalStopReason) {
         final long elapsedNowMillis = sElapsedRealtimeClock.millis();
         final JobInfo job = failureToReschedule.getJob();
 
         final long initialBackoffMillis = job.getInitialBackoffMillis();
-        final int backoffAttempts = failureToReschedule.getNumFailures() + 1;
+        int numFailures = failureToReschedule.getNumFailures();
+        int numSystemStops = failureToReschedule.getNumSystemStops();
+        // We should back off slowly if JobScheduler keeps stopping the job,
+        // but back off immediately if the issue appeared to be the app's fault.
+        if (internalStopReason == JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH
+                || internalStopReason == JobParameters.INTERNAL_STOP_REASON_TIMEOUT) {
+            numFailures++;
+        } else {
+            numSystemStops++;
+        }
+        final int backoffAttempts = Math.max(1,
+                numFailures + numSystemStops / mConstants.SYSTEM_STOP_TO_FAILURE_RATIO);
         long delayMillis;
 
         switch (job.getBackoffPolicy()) {
@@ -1943,7 +1969,7 @@
                 Math.min(delayMillis, JobInfo.MAX_BACKOFF_DELAY_MILLIS);
         JobStatus newJob = new JobStatus(failureToReschedule,
                 elapsedNowMillis + delayMillis,
-                JobStatus.NO_LATEST_RUNTIME, backoffAttempts,
+                JobStatus.NO_LATEST_RUNTIME, numFailures, numSystemStops,
                 failureToReschedule.getLastSuccessfulRunTime(), sSystemClock.millis());
         if (job.isPeriodic()) {
             newJob.setOriginalLatestRunTimeElapsed(
@@ -2034,7 +2060,7 @@
                     + newLatestRuntimeElapsed);
             return new JobStatus(periodicToReschedule,
                     elapsedNow + period - flex, elapsedNow + period,
-                    0 /* backoffAttempt */,
+                    0 /* numFailures */, 0 /* numSystemStops */,
                     sSystemClock.millis() /* lastSuccessfulRunTime */,
                     periodicToReschedule.getLastFailedRunTime());
         }
@@ -2049,7 +2075,7 @@
         }
         return new JobStatus(periodicToReschedule,
                 newEarliestRunTimeElapsed, newLatestRuntimeElapsed,
-                0 /* backoffAttempt */,
+                0 /* numFailures */, 0 /* numSystemStops */,
                 sSystemClock.millis() /* lastSuccessfulRunTime */,
                 periodicToReschedule.getLastFailedRunTime());
     }
@@ -2093,7 +2119,7 @@
         // job so we can transfer any appropriate state over from the previous job when
         // we stop it.
         final JobStatus rescheduledJob = needsReschedule
-                ? getRescheduleJobForFailureLocked(jobStatus) : null;
+                ? getRescheduleJobForFailureLocked(jobStatus, debugStopReason) : null;
         if (rescheduledJob != null
                 && (debugStopReason == JobParameters.INTERNAL_STOP_REASON_TIMEOUT
                 || debugStopReason == JobParameters.INTERNAL_STOP_REASON_PREEMPT)) {
@@ -2155,11 +2181,11 @@
 
     @Override
     public void onRestrictionStateChanged(@NonNull JobRestriction restriction,
-            boolean stopLongRunningJobs) {
+            boolean stopOvertimeJobs) {
         mHandler.obtainMessage(MSG_CHECK_JOB).sendToTarget();
-        if (stopLongRunningJobs) {
+        if (stopOvertimeJobs) {
             synchronized (mLock) {
-                mConcurrencyManager.maybeStopLongRunningJobsLocked(restriction);
+                mConcurrencyManager.maybeStopOvertimeJobsLocked(restriction);
             }
         }
     }
@@ -2427,7 +2453,7 @@
                     shouldForceBatchJob =
                             mPrefetchController.getNextEstimatedLaunchTimeLocked(job)
                                     > relativelySoonCutoffTime;
-                } else if (job.getNumFailures() > 0) {
+                } else if (job.getNumPreviousAttempts() > 0) {
                     shouldForceBatchJob = false;
                 } else {
                     final long nowElapsed = sElapsedRealtimeClock.millis();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
index d6456f0..9e3f19d 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobServiceContext.java
@@ -363,7 +363,7 @@
                     job.getJob().isPrefetch(),
                     job.getJob().getPriority(),
                     job.getEffectivePriority(),
-                    job.getNumFailures());
+                    job.getNumPreviousAttempts());
             if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
                 // Use the context's ID to distinguish traces since there'll only be one job
                 // running per context.
@@ -1032,7 +1032,7 @@
                 completedJob.getJob().isPrefetch(),
                 completedJob.getJob().getPriority(),
                 completedJob.getEffectivePriority(),
-                completedJob.getNumFailures());
+                completedJob.getNumPreviousAttempts());
         if (Trace.isTagEnabled(Trace.TRACE_TAG_SYSTEM_SERVER)) {
             Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_SYSTEM_SERVER, "JobScheduler",
                     completedJob.getTag(), getId());
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
index 22b0968..68cb049 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobStore.java
@@ -194,7 +194,7 @@
                         convertRtcBoundsToElapsed(utcTimes, elapsedNow);
                 JobStatus newJob = new JobStatus(job,
                         elapsedRuntimes.first, elapsedRuntimes.second,
-                        0, job.getLastSuccessfulRunTime(), job.getLastFailedRunTime());
+                        0, 0, job.getLastSuccessfulRunTime(), job.getLastFailedRunTime());
                 newJob.prepareLocked();
                 toAdd.add(newJob);
                 toRemove.add(job);
@@ -203,13 +203,12 @@
     }
 
     /**
-     * Add a job to the master list, persisting it if necessary. If the JobStatus already exists,
-     * it will be replaced.
+     * Add a job to the master list, persisting it if necessary.
+     * Similar jobs to the new job will not be removed.
+     *
      * @param jobStatus Job to add.
-     * @return Whether or not an equivalent JobStatus was replaced by this operation.
      */
-    public boolean add(JobStatus jobStatus) {
-        boolean replaced = mJobSet.remove(jobStatus);
+    public void add(JobStatus jobStatus) {
         mJobSet.add(jobStatus);
         if (jobStatus.isPersisted()) {
             maybeWriteStatusToDiskAsync();
@@ -217,7 +216,6 @@
         if (DEBUG) {
             Slog.d(TAG, "Added job status to store: " + jobStatus);
         }
-        return replaced;
     }
 
     /**
diff --git a/apex/jobscheduler/service/java/com/android/server/job/StateChangedListener.java b/apex/jobscheduler/service/java/com/android/server/job/StateChangedListener.java
index d7bd030..554f152 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/StateChangedListener.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/StateChangedListener.java
@@ -41,11 +41,11 @@
      * Called by a {@link com.android.server.job.restrictions.JobRestriction} to notify the
      * JobScheduler that it should check on the state of all jobs.
      *
-     * @param stopLongRunningJobs Whether to stop any jobs that have run for more than their minimum
-     *                            execution guarantee and are restricted by the changed restriction
+     * @param stopOvertimeJobs Whether to stop any jobs that have run for more than their minimum
+     *                         execution guarantee and are restricted by the changed restriction
      */
     void onRestrictionStateChanged(@NonNull JobRestriction restriction,
-            boolean stopLongRunningJobs);
+            boolean stopOvertimeJobs);
 
     /**
      * Called by the controller to notify the JobManager that regardless of the state of the task,
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
index 65d7121..ecee10a 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
@@ -81,8 +81,7 @@
     }
 
     @Override
-    public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
-            boolean forUpdate) {
+    public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob) {
     }
 
     @Override
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java
index d284a99..2ca3f8f 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BatteryController.java
@@ -133,7 +133,7 @@
     }
 
     @Override
-    public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob, boolean forUpdate) {
+    public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob) {
         if (taskStatus.clearTrackingController(JobStatus.TRACKING_BATTERY)) {
             mTrackedTasks.remove(taskStatus);
             mTopStartedJobs.remove(taskStatus);
@@ -143,7 +143,7 @@
     @Override
     public void stopTrackingRestrictedJobLocked(JobStatus jobStatus) {
         if (!jobStatus.hasPowerConstraint()) {
-            maybeStopTrackingJobLocked(jobStatus, null, false);
+            maybeStopTrackingJobLocked(jobStatus, null);
         }
     }
 
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java
index 9b59560..b029e00 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ComponentController.java
@@ -127,8 +127,7 @@
     }
 
     @Override
-    public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
-            boolean forUpdate) {
+    public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob) {
     }
 
     @Override
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
index d2dc2a7e..16dd1672 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ConnectivityController.java
@@ -290,8 +290,7 @@
 
     @GuardedBy("mLock")
     @Override
-    public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
-            boolean forUpdate) {
+    public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob) {
         if (jobStatus.clearTrackingController(JobStatus.TRACKING_CONNECTIVITY)) {
             ArraySet<JobStatus> jobs = mTrackedJobs.get(jobStatus.getSourceUid());
             if (jobs != null) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java
index 83a756c..847a1bf 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/ContentObserverController.java
@@ -159,8 +159,7 @@
     }
 
     @Override
-    public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob,
-            boolean forUpdate) {
+    public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob) {
         if (taskStatus.clearTrackingController(JobStatus.TRACKING_CONTENT)) {
             mTrackedTasks.remove(taskStatus);
             if (taskStatus.contentObserverJobInstance != null) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java
index abbe177..bdf72b6 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/DeviceIdleJobsController.java
@@ -225,8 +225,7 @@
     }
 
     @Override
-    public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
-            boolean forUpdate) {
+    public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob) {
         if ((jobStatus.getFlags()&JobInfo.FLAG_IMPORTANT_WHILE_FOREGROUND) != 0) {
             mAllowInIdleJobs.remove(jobStatus);
         }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
index 547f94ba..4c17692 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/FlexibilityController.java
@@ -213,7 +213,7 @@
 
     @Override
     @GuardedBy("mLock")
-    public void maybeStopTrackingJobLocked(JobStatus js, JobStatus incomingJob, boolean forUpdate) {
+    public void maybeStopTrackingJobLocked(JobStatus js, JobStatus incomingJob) {
         if (js.clearTrackingController(JobStatus.TRACKING_FLEXIBILITY)) {
             mFlexibilityAlarmQueue.removeAlarmForKey(js);
             mFlexibilityTracker.remove(js);
@@ -342,10 +342,10 @@
             // There is no deadline and no estimated launch time.
             return NO_LIFECYCLE_END;
         }
-        if (js.getNumFailures() > 1) {
-            // Number of failures will not equal one as per restriction in JobStatus constructor.
+        // Increase the flex deadline for jobs rescheduled more than once.
+        if (js.getNumPreviousAttempts() > 1) {
             return earliest + Math.min(
-                    (long) Math.scalb(mRescheduledJobDeadline, js.getNumFailures() - 2),
+                    (long) Math.scalb(mRescheduledJobDeadline, js.getNumPreviousAttempts() - 2),
                     mMaxRescheduledDeadline);
         }
         return js.getLatestRunTimeElapsed() == JobStatus.NO_LATEST_RUNTIME
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/IdleController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/IdleController.java
index 926cfc1..a25af71 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/IdleController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/IdleController.java
@@ -76,8 +76,7 @@
     }
 
     @Override
-    public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob,
-            boolean forUpdate) {
+    public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob) {
         if (taskStatus.clearTrackingController(JobStatus.TRACKING_IDLE)) {
             mTrackedTasks.remove(taskStatus);
         }
@@ -86,7 +85,7 @@
     @Override
     public void stopTrackingRestrictedJobLocked(JobStatus jobStatus) {
         if (!jobStatus.hasIdleConstraint()) {
-            maybeStopTrackingJobLocked(jobStatus, null, false);
+            maybeStopTrackingJobLocked(jobStatus, null);
         }
     }
 
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
index de602a8..92716f4 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/JobStatus.java
@@ -241,10 +241,22 @@
      */
     private long mOriginalLatestRunTimeElapsedMillis;
 
-    /** How many times this job has failed, used to compute back-off. */
+    /**
+     * How many times this job has failed to complete on its own
+     * (via {@link android.app.job.JobService#jobFinished(JobParameters, boolean)} or because of
+     * a timeout).
+     * This count doesn't include most times JobScheduler decided to stop the job
+     * (via {@link android.app.job.JobService#onStopJob(JobParameters)}.
+     */
     private final int numFailures;
 
     /**
+     * The number of times JobScheduler has forced this job to stop due to reasons mostly outside
+     * of the app's control.
+     */
+    private final int mNumSystemStops;
+
+    /**
      * Which app standby bucket this job's app is in.  Updated when the app is moved to a
      * different bucket.
      */
@@ -488,6 +500,8 @@
      * @param tag A string associated with the job for debugging/logging purposes.
      * @param numFailures Count of how many times this job has requested a reschedule because
      *     its work was not yet finished.
+     * @param numSystemStops Count of how many times JobScheduler has forced this job to stop due to
+     *     factors mostly out of the app's control.
      * @param earliestRunTimeElapsedMillis Milestone: earliest point in time at which the job
      *     is to be considered runnable
      * @param latestRunTimeElapsedMillis Milestone: point in time at which the job will be
@@ -497,7 +511,7 @@
      * @param internalFlags Non-API property flags about this job
      */
     private JobStatus(JobInfo job, int callingUid, String sourcePackageName,
-            int sourceUserId, int standbyBucket, String tag, int numFailures,
+            int sourceUserId, int standbyBucket, String tag, int numFailures, int numSystemStops,
             long earliestRunTimeElapsedMillis, long latestRunTimeElapsedMillis,
             long lastSuccessfulRunTime, long lastFailedRunTime, int internalFlags,
             int dynamicConstraints) {
@@ -535,6 +549,7 @@
         this.latestRunTimeElapsedMillis = latestRunTimeElapsedMillis;
         this.mOriginalLatestRunTimeElapsedMillis = latestRunTimeElapsedMillis;
         this.numFailures = numFailures;
+        mNumSystemStops = numSystemStops;
 
         int requiredConstraints = job.getConstraintFlags();
         if (job.getRequiredNetwork() != null) {
@@ -576,7 +591,7 @@
         // Otherwise, every consecutive reschedule increases a jobs' flexibility deadline.
         if (!isRequestedExpeditedJob()
                 && satisfiesMinWindowException
-                && numFailures != 1
+                && (numFailures + numSystemStops) != 1
                 && lacksSomeFlexibleConstraints) {
             mNumRequiredFlexibleConstraints =
                     NUM_SYSTEM_WIDE_FLEXIBLE_CONSTRAINTS + (mPreferUnmetered ? 1 : 0);
@@ -626,7 +641,7 @@
         this(jobStatus.getJob(), jobStatus.getUid(),
                 jobStatus.getSourcePackageName(), jobStatus.getSourceUserId(),
                 jobStatus.getStandbyBucket(),
-                jobStatus.getSourceTag(), jobStatus.getNumFailures(),
+                jobStatus.getSourceTag(), jobStatus.getNumFailures(), jobStatus.getNumSystemStops(),
                 jobStatus.getEarliestRunTime(), jobStatus.getLatestRunTimeElapsed(),
                 jobStatus.getLastSuccessfulRunTime(), jobStatus.getLastFailedRunTime(),
                 jobStatus.getInternalFlags(), jobStatus.mDynamicConstraints);
@@ -654,7 +669,7 @@
             int innerFlags, int dynamicConstraints) {
         this(job, callingUid, sourcePkgName, sourceUserId,
                 standbyBucket,
-                sourceTag, 0,
+                sourceTag, /* numFailures */ 0, /* numSystemStops */ 0,
                 earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis,
                 lastSuccessfulRunTime, lastFailedRunTime, innerFlags, dynamicConstraints);
 
@@ -673,12 +688,13 @@
     /** Create a new job to be rescheduled with the provided parameters. */
     public JobStatus(JobStatus rescheduling,
             long newEarliestRuntimeElapsedMillis,
-            long newLatestRuntimeElapsedMillis, int backoffAttempt,
+            long newLatestRuntimeElapsedMillis, int numFailures, int numSystemStops,
             long lastSuccessfulRunTime, long lastFailedRunTime) {
         this(rescheduling.job, rescheduling.getUid(),
                 rescheduling.getSourcePackageName(), rescheduling.getSourceUserId(),
                 rescheduling.getStandbyBucket(),
-                rescheduling.getSourceTag(), backoffAttempt, newEarliestRuntimeElapsedMillis,
+                rescheduling.getSourceTag(), numFailures, numSystemStops,
+                newEarliestRuntimeElapsedMillis,
                 newLatestRuntimeElapsedMillis,
                 lastSuccessfulRunTime, lastFailedRunTime, rescheduling.getInternalFlags(),
                 rescheduling.mDynamicConstraints);
@@ -715,7 +731,7 @@
         int standbyBucket = JobSchedulerService.standbyBucketForPackage(jobPackage,
                 sourceUserId, elapsedNow);
         return new JobStatus(job, callingUid, sourcePkg, sourceUserId,
-                standbyBucket, tag, 0,
+                standbyBucket, tag, /* numFailures */ 0, /* numSystemStops */ 0,
                 earliestRunTimeElapsedMillis, latestRunTimeElapsedMillis,
                 0 /* lastSuccessfulRunTime */, 0 /* lastFailedRunTime */,
                 /*innerFlags=*/ 0, /* dynamicConstraints */ 0);
@@ -868,10 +884,27 @@
         pw.print(job.getId());
     }
 
+    /**
+     * Returns the number of times the job stopped previously for reasons that appeared to be within
+     * the app's control.
+     */
     public int getNumFailures() {
         return numFailures;
     }
 
+    /**
+     * Returns the number of times the system stopped a previous execution of this job for reasons
+     * that were likely outside the app's control.
+     */
+    public int getNumSystemStops() {
+        return mNumSystemStops;
+    }
+
+    /** Returns the total number of times we've attempted to run this job in the past. */
+    public int getNumPreviousAttempts() {
+        return numFailures + mNumSystemStops;
+    }
+
     public ComponentName getServiceComponent() {
         return job.getService();
     }
@@ -1061,27 +1094,41 @@
 
     private void updateNetworkBytesLocked() {
         mTotalNetworkDownloadBytes = job.getEstimatedNetworkDownloadBytes();
+        if (mTotalNetworkDownloadBytes < 0) {
+            // Legacy apps may have provided invalid negative values. Ignore invalid values.
+            mTotalNetworkDownloadBytes = JobInfo.NETWORK_BYTES_UNKNOWN;
+        }
         mTotalNetworkUploadBytes = job.getEstimatedNetworkUploadBytes();
+        if (mTotalNetworkUploadBytes < 0) {
+            // Legacy apps may have provided invalid negative values. Ignore invalid values.
+            mTotalNetworkUploadBytes = JobInfo.NETWORK_BYTES_UNKNOWN;
+        }
+        // Minimum network chunk bytes has had data validation since its introduction, so no
+        // need to do validation again.
         mMinimumNetworkChunkBytes = job.getMinimumNetworkChunkBytes();
 
         if (pendingWork != null) {
             for (int i = 0; i < pendingWork.size(); i++) {
-                if (mTotalNetworkDownloadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
-                    // If any component of the job has unknown usage, we don't have a
-                    // complete picture of what data will be used, and we have to treat the
-                    // entire up/download as unknown.
-                    long downloadBytes = pendingWork.get(i).getEstimatedNetworkDownloadBytes();
-                    if (downloadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
+                long downloadBytes = pendingWork.get(i).getEstimatedNetworkDownloadBytes();
+                if (downloadBytes != JobInfo.NETWORK_BYTES_UNKNOWN && downloadBytes > 0) {
+                    // If any component of the job has unknown usage, we won't have a
+                    // complete picture of what data will be used. However, we use what we are given
+                    // to get us as close to the complete picture as possible.
+                    if (mTotalNetworkDownloadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
                         mTotalNetworkDownloadBytes += downloadBytes;
+                    } else {
+                        mTotalNetworkDownloadBytes = downloadBytes;
                     }
                 }
-                if (mTotalNetworkUploadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
-                    // If any component of the job has unknown usage, we don't have a
-                    // complete picture of what data will be used, and we have to treat the
-                    // entire up/download as unknown.
-                    long uploadBytes = pendingWork.get(i).getEstimatedNetworkUploadBytes();
-                    if (uploadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
+                long uploadBytes = pendingWork.get(i).getEstimatedNetworkUploadBytes();
+                if (uploadBytes != JobInfo.NETWORK_BYTES_UNKNOWN && uploadBytes > 0) {
+                    // If any component of the job has unknown usage, we won't have a
+                    // complete picture of what data will be used. However, we use what we are given
+                    // to get us as close to the complete picture as possible.
+                    if (mTotalNetworkUploadBytes != JobInfo.NETWORK_BYTES_UNKNOWN) {
                         mTotalNetworkUploadBytes += uploadBytes;
+                    } else {
+                        mTotalNetworkUploadBytes = uploadBytes;
                     }
                 }
                 final long chunkBytes = pendingWork.get(i).getMinimumNetworkChunkBytes();
@@ -1857,6 +1904,10 @@
             sb.append(" failures=");
             sb.append(numFailures);
         }
+        if (mNumSystemStops != 0) {
+            sb.append(" system stops=");
+            sb.append(mNumSystemStops);
+        }
         if (isReady()) {
             sb.append(" READY");
         } else {
@@ -2382,6 +2433,9 @@
         if (numFailures != 0) {
             pw.print("Num failures: "); pw.println(numFailures);
         }
+        if (mNumSystemStops != 0) {
+            pw.print("Num system stops: "); pw.println(mNumSystemStops);
+        }
         if (mLastSuccessfulRunTime != 0) {
             pw.print("Last successful run: ");
             pw.println(formatTime(mLastSuccessfulRunTime));
@@ -2579,7 +2633,7 @@
         proto.write(JobStatusDumpProto.ORIGINAL_LATEST_RUNTIME_ELAPSED,
                 mOriginalLatestRunTimeElapsedMillis);
 
-        proto.write(JobStatusDumpProto.NUM_FAILURES, numFailures);
+        proto.write(JobStatusDumpProto.NUM_FAILURES, numFailures + mNumSystemStops);
         proto.write(JobStatusDumpProto.LAST_SUCCESSFUL_RUN_TIME, mLastSuccessfulRunTime);
         proto.write(JobStatusDumpProto.LAST_FAILED_RUN_TIME, mLastFailedRunTime);
 
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/Package.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/Package.java
deleted file mode 100644
index 78a77fe..0000000
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/Package.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.job.controllers;
-
-import java.util.Objects;
-
-/** Wrapper class to represent a userId-pkgName combo. */
-final class Package {
-    public final String packageName;
-    public final int userId;
-
-    Package(int userId, String packageName) {
-        this.userId = userId;
-        this.packageName = packageName;
-    }
-
-    @Override
-    public String toString() {
-        return packageToString(userId, packageName);
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (this == obj) {
-            return true;
-        }
-        if (!(obj instanceof Package)) {
-            return false;
-        }
-        Package other = (Package) obj;
-        return userId == other.userId && Objects.equals(packageName, other.packageName);
-    }
-
-    @Override
-    public int hashCode() {
-        return packageName.hashCode() + userId;
-    }
-
-    /**
-     * Standardize the output of userId-packageName combo.
-     */
-    static String packageToString(int userId, String packageName) {
-        return "<" + userId + ">" + packageName;
-    }
-}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java
index e04cec3..c46ffd7 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/PrefetchController.java
@@ -21,7 +21,6 @@
 
 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
 import static com.android.server.job.JobSchedulerService.sSystemClock;
-import static com.android.server.job.controllers.Package.packageToString;
 
 import android.annotation.CurrentTimeMillisLong;
 import android.annotation.ElapsedRealtimeLong;
@@ -31,6 +30,7 @@
 import android.app.usage.UsageStatsManagerInternal.EstimatedLaunchTimeChangedListener;
 import android.appwidget.AppWidgetManager;
 import android.content.Context;
+import android.content.pm.UserPackage;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -167,13 +167,12 @@
 
     @Override
     @GuardedBy("mLock")
-    public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
-            boolean forUpdate) {
+    public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob) {
         final int userId = jobStatus.getSourceUserId();
         final String pkgName = jobStatus.getSourcePackageName();
         final ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, pkgName);
         if (jobs != null && jobs.remove(jobStatus) && jobs.size() == 0) {
-            mThresholdAlarmListener.removeAlarmForKey(new Package(userId, pkgName));
+            mThresholdAlarmListener.removeAlarmForKey(UserPackage.of(userId, pkgName));
         }
     }
 
@@ -187,7 +186,7 @@
         final int userId = UserHandle.getUserId(uid);
         mTrackedJobs.delete(userId, packageName);
         mEstimatedLaunchTimes.delete(userId, packageName);
-        mThresholdAlarmListener.removeAlarmForKey(new Package(userId, packageName));
+        mThresholdAlarmListener.removeAlarmForKey(UserPackage.of(userId, packageName));
     }
 
     @Override
@@ -355,7 +354,7 @@
             @CurrentTimeMillisLong long now, @ElapsedRealtimeLong long nowElapsed) {
         final ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, pkgName);
         if (jobs == null || jobs.size() == 0) {
-            mThresholdAlarmListener.removeAlarmForKey(new Package(userId, pkgName));
+            mThresholdAlarmListener.removeAlarmForKey(UserPackage.of(userId, pkgName));
             return;
         }
 
@@ -366,10 +365,10 @@
             // Set alarm to be notified when this crosses the threshold.
             final long timeToCrossThresholdMs =
                     nextEstimatedLaunchTime - (now + mLaunchTimeThresholdMs);
-            mThresholdAlarmListener.addAlarm(new Package(userId, pkgName),
+            mThresholdAlarmListener.addAlarm(UserPackage.of(userId, pkgName),
                     nowElapsed + timeToCrossThresholdMs);
         } else {
-            mThresholdAlarmListener.removeAlarmForKey(new Package(userId, pkgName));
+            mThresholdAlarmListener.removeAlarmForKey(UserPackage.of(userId, pkgName));
         }
     }
 
@@ -428,25 +427,25 @@
     }
 
     /** Track when apps will cross the "will run soon" threshold. */
-    private class ThresholdAlarmListener extends AlarmQueue<Package> {
+    private class ThresholdAlarmListener extends AlarmQueue<UserPackage> {
         private ThresholdAlarmListener(Context context, Looper looper) {
             super(context, looper, "*job.prefetch*", "Prefetch threshold", false,
                     PcConstants.DEFAULT_LAUNCH_TIME_THRESHOLD_MS / 10);
         }
 
         @Override
-        protected boolean isForUser(@NonNull Package key, int userId) {
+        protected boolean isForUser(@NonNull UserPackage key, int userId) {
             return key.userId == userId;
         }
 
         @Override
-        protected void processExpiredAlarms(@NonNull ArraySet<Package> expired) {
+        protected void processExpiredAlarms(@NonNull ArraySet<UserPackage> expired) {
             final ArraySet<JobStatus> changedJobs = new ArraySet<>();
             synchronized (mLock) {
                 final long now = sSystemClock.millis();
                 final long nowElapsed = sElapsedRealtimeClock.millis();
                 for (int i = 0; i < expired.size(); ++i) {
-                    Package p = expired.valueAt(i);
+                    UserPackage p = expired.valueAt(i);
                     if (!willBeLaunchedSoonLocked(p.userId, p.packageName, now)) {
                         Slog.e(TAG, "Alarm expired for "
                                 + packageToString(p.userId, p.packageName) + " at the wrong time");
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
index bb8d175..d8206ad 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/QuotaController.java
@@ -28,7 +28,6 @@
 import static com.android.server.job.JobSchedulerService.RESTRICTED_INDEX;
 import static com.android.server.job.JobSchedulerService.WORKING_INDEX;
 import static com.android.server.job.JobSchedulerService.sElapsedRealtimeClock;
-import static com.android.server.job.controllers.Package.packageToString;
 
 import android.Manifest;
 import android.annotation.NonNull;
@@ -44,6 +43,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.UserPackage;
 import android.os.BatteryManager;
 import android.os.Handler;
 import android.os.Looper;
@@ -532,7 +532,7 @@
      */
     private final SparseSetArray<String> mSystemInstallers = new SparseSetArray<>();
 
-    /** An app has reached its quota. The message should contain a {@link Package} object. */
+    /** An app has reached its quota. The message should contain a {@link UserPackage} object. */
     @VisibleForTesting
     static final int MSG_REACHED_QUOTA = 0;
     /** Drop any old timing sessions. */
@@ -542,7 +542,7 @@
     /** Process state for a UID has changed. */
     private static final int MSG_UID_PROCESS_STATE_CHANGED = 3;
     /**
-     * An app has reached its expedited job quota. The message should contain a {@link Package}
+     * An app has reached its expedited job quota. The message should contain a {@link UserPackage}
      * object.
      */
     @VisibleForTesting
@@ -673,15 +673,14 @@
 
     @Override
     @GuardedBy("mLock")
-    public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
-            boolean forUpdate) {
+    public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob) {
         if (jobStatus.clearTrackingController(JobStatus.TRACKING_QUOTA)) {
             unprepareFromExecutionLocked(jobStatus);
             final int userId = jobStatus.getSourceUserId();
             final String pkgName = jobStatus.getSourcePackageName();
             ArraySet<JobStatus> jobs = mTrackedJobs.get(userId, pkgName);
             if (jobs != null && jobs.remove(jobStatus) && jobs.size() == 0) {
-                mInQuotaAlarmQueue.removeAlarmForKey(new Package(userId, pkgName));
+                mInQuotaAlarmQueue.removeAlarmForKey(UserPackage.of(userId, pkgName));
             }
         }
     }
@@ -747,7 +746,7 @@
         }
         mTimingEvents.delete(userId, packageName);
         mEJTimingSessions.delete(userId, packageName);
-        mInQuotaAlarmQueue.removeAlarmForKey(new Package(userId, packageName));
+        mInQuotaAlarmQueue.removeAlarmForKey(UserPackage.of(userId, packageName));
         mExecutionStatsCache.delete(userId, packageName);
         mEJStats.delete(userId, packageName);
         mTopAppTrackers.delete(userId, packageName);
@@ -1726,7 +1725,7 @@
             // exempted.
             maybeScheduleStartAlarmLocked(userId, packageName, realStandbyBucket);
         } else {
-            mInQuotaAlarmQueue.removeAlarmForKey(new Package(userId, packageName));
+            mInQuotaAlarmQueue.removeAlarmForKey(UserPackage.of(userId, packageName));
         }
         return changedJobs;
     }
@@ -1765,7 +1764,7 @@
                     && isWithinQuotaLocked(userId, packageName, realStandbyBucket)) {
                 // TODO(141645789): we probably shouldn't cancel the alarm until we've verified
                 // that all jobs for the userId-package are within quota.
-                mInQuotaAlarmQueue.removeAlarmForKey(new Package(userId, packageName));
+                mInQuotaAlarmQueue.removeAlarmForKey(UserPackage.of(userId, packageName));
             } else {
                 mToScheduleStartAlarms.add(userId, packageName, realStandbyBucket);
             }
@@ -1815,7 +1814,7 @@
         if (jobs == null || jobs.size() == 0) {
             Slog.e(TAG, "maybeScheduleStartAlarmLocked called for "
                     + packageToString(userId, packageName) + " that has no jobs");
-            mInQuotaAlarmQueue.removeAlarmForKey(new Package(userId, packageName));
+            mInQuotaAlarmQueue.removeAlarmForKey(UserPackage.of(userId, packageName));
             return;
         }
 
@@ -1839,7 +1838,7 @@
                         + getRemainingExecutionTimeLocked(userId, packageName, standbyBucket)
                         + "ms in its quota.");
             }
-            mInQuotaAlarmQueue.removeAlarmForKey(new Package(userId, packageName));
+            mInQuotaAlarmQueue.removeAlarmForKey(UserPackage.of(userId, packageName));
             mHandler.obtainMessage(MSG_CHECK_PACKAGE, userId, 0, packageName).sendToTarget();
             return;
         }
@@ -1904,7 +1903,7 @@
                             + nowElapsed + ", inQuotaTime=" + inQuotaTimeElapsed + ": " + stats);
             inQuotaTimeElapsed = nowElapsed + 5 * MINUTE_IN_MILLIS;
         }
-        mInQuotaAlarmQueue.addAlarm(new Package(userId, packageName), inQuotaTimeElapsed);
+        mInQuotaAlarmQueue.addAlarm(UserPackage.of(userId, packageName), inQuotaTimeElapsed);
     }
 
     private boolean setConstraintSatisfied(@NonNull JobStatus jobStatus, long nowElapsed,
@@ -2099,7 +2098,7 @@
     }
 
     private final class Timer {
-        private final Package mPkg;
+        private final UserPackage mPkg;
         private final int mUid;
         private final boolean mRegularJobTimer;
 
@@ -2111,7 +2110,7 @@
         private long mDebitAdjustment;
 
         Timer(int uid, int userId, String packageName, boolean regularJobTimer) {
-            mPkg = new Package(userId, packageName);
+            mPkg = UserPackage.of(userId, packageName);
             mUid = uid;
             mRegularJobTimer = regularJobTimer;
         }
@@ -2366,7 +2365,7 @@
     }
 
     private final class TopAppTimer {
-        private final Package mPkg;
+        private final UserPackage mPkg;
 
         // List of jobs currently running for this app that started when the app wasn't in the
         // foreground.
@@ -2374,7 +2373,7 @@
         private long mStartTimeElapsed;
 
         TopAppTimer(int userId, String packageName) {
-            mPkg = new Package(userId, packageName);
+            mPkg = UserPackage.of(userId, packageName);
         }
 
         private int calculateTimeChunks(final long nowElapsed) {
@@ -2657,7 +2656,7 @@
             synchronized (mLock) {
                 switch (msg.what) {
                     case MSG_REACHED_QUOTA: {
-                        Package pkg = (Package) msg.obj;
+                        UserPackage pkg = (UserPackage) msg.obj;
                         if (DEBUG) {
                             Slog.d(TAG, "Checking if " + pkg + " has reached its quota.");
                         }
@@ -2686,7 +2685,7 @@
                         break;
                     }
                     case MSG_REACHED_EJ_QUOTA: {
-                        Package pkg = (Package) msg.obj;
+                        UserPackage pkg = (UserPackage) msg.obj;
                         if (DEBUG) {
                             Slog.d(TAG, "Checking if " + pkg + " has reached its EJ quota.");
                         }
@@ -2888,21 +2887,21 @@
     }
 
     /** Track when UPTCs are expected to come back into quota. */
-    private class InQuotaAlarmQueue extends AlarmQueue<Package> {
+    private class InQuotaAlarmQueue extends AlarmQueue<UserPackage> {
         private InQuotaAlarmQueue(Context context, Looper looper) {
             super(context, looper, ALARM_TAG_QUOTA_CHECK, "In quota", false,
                     QcConstants.DEFAULT_MIN_QUOTA_CHECK_DELAY_MS);
         }
 
         @Override
-        protected boolean isForUser(@NonNull Package key, int userId) {
+        protected boolean isForUser(@NonNull UserPackage key, int userId) {
             return key.userId == userId;
         }
 
         @Override
-        protected void processExpiredAlarms(@NonNull ArraySet<Package> expired) {
+        protected void processExpiredAlarms(@NonNull ArraySet<UserPackage> expired) {
             for (int i = 0; i < expired.size(); ++i) {
-                Package p = expired.valueAt(i);
+                UserPackage p = expired.valueAt(i);
                 mHandler.obtainMessage(MSG_CHECK_PACKAGE, p.userId, 0, p.packageName)
                         .sendToTarget();
             }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java
index 8453e53..44ac798 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/StateController.java
@@ -85,8 +85,7 @@
     /**
      * Remove task - this will happen if the task is cancelled, completed, etc.
      */
-    public abstract void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
-            boolean forUpdate);
+    public abstract void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob);
 
     /**
      * Called when a new job is being created to reschedule an old failed job.
@@ -187,4 +186,11 @@
     /** Dump any internal constants the Controller may have. */
     public void dumpConstants(ProtoOutputStream proto) {
     }
+
+    /**
+     * Standardize the output of userId-packageName combo.
+     */
+    static String packageToString(int userId, String packageName) {
+        return "<" + userId + ">" + packageName;
+    }
 }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/StorageController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/StorageController.java
index 1ce0a7f6..11e2ff7 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/StorageController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/StorageController.java
@@ -70,8 +70,7 @@
     }
 
     @Override
-    public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob,
-            boolean forUpdate) {
+    public void maybeStopTrackingJobLocked(JobStatus taskStatus, JobStatus incomingJob) {
         if (taskStatus.clearTrackingController(JobStatus.TRACKING_STORAGE)) {
             mTrackedTasks.remove(taskStatus);
         }
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/TareController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/TareController.java
index b2ca3a0..cafb02d 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/TareController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/TareController.java
@@ -383,8 +383,7 @@
 
     @Override
     @GuardedBy("mLock")
-    public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
-            boolean forUpdate) {
+    public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob) {
         final int userId = jobStatus.getSourceUserId();
         final String pkgName = jobStatus.getSourcePackageName();
         if (!mTopStartedJobs.remove(jobStatus) && jobStatus.madeActive > 0) {
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java
index b6361ce..5195f28 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/TimeController.java
@@ -79,7 +79,7 @@
     @Override
     public void maybeStartTrackingJobLocked(JobStatus job, JobStatus lastJob) {
         if (job.hasTimingDelayConstraint() || job.hasDeadlineConstraint()) {
-            maybeStopTrackingJobLocked(job, null, false);
+            maybeStopTrackingJobLocked(job, null);
 
             // First: check the constraints now, because if they are already satisfied
             // then there is no need to track it.  This gives us a fast path for a common
@@ -134,8 +134,7 @@
      * tracking was the one our alarms were based off of.
      */
     @Override
-    public void maybeStopTrackingJobLocked(JobStatus job, JobStatus incomingJob,
-            boolean forUpdate) {
+    public void maybeStopTrackingJobLocked(JobStatus job, JobStatus incomingJob) {
         if (job.clearTrackingController(JobStatus.TRACKING_TIME)) {
             if (mTrackedJobs.remove(job)) {
                 checkExpiredDelaysAndResetAlarm();
diff --git a/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java b/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
index a007a69..ca2fd60 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/restrictions/ThermalStatusRestriction.java
@@ -90,11 +90,11 @@
         final int priority = job.getEffectivePriority();
         if (mThermalStatus >= HIGHER_PRIORITY_THRESHOLD) {
             // For moderate throttling, only let expedited jobs and high priority regular jobs that
-            // haven't been running for long run.
+            // haven't been running for a long time run.
             return !job.shouldTreatAsExpeditedJob()
                     && !(priority == JobInfo.PRIORITY_HIGH
                         && mService.isCurrentlyRunningLocked(job)
-                        && !mService.isLongRunningLocked(job));
+                        && !mService.isJobInOvertimeLocked(job));
         }
         if (mThermalStatus >= LOW_PRIORITY_THRESHOLD) {
             // For light throttling, throttle all min priority jobs and all low priority jobs that
@@ -102,7 +102,7 @@
             return priority == JobInfo.PRIORITY_MIN
                     || (priority == JobInfo.PRIORITY_LOW
                         && (!mService.isCurrentlyRunningLocked(job)
-                            || mService.isLongRunningLocked(job)));
+                            || mService.isJobInOvertimeLocked(job)));
         }
         return false;
     }
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
index 7a13e3f..abc196f 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
@@ -36,6 +36,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
+import android.content.pm.UserPackage;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
@@ -824,7 +825,7 @@
     void onPackageRemovedLocked(final int userId, @NonNull final String pkgName) {
         mScribe.discardLedgerLocked(userId, pkgName);
         mCurrentOngoingEvents.delete(userId, pkgName);
-        mBalanceThresholdAlarmQueue.removeAlarmForKey(new Package(userId, pkgName));
+        mBalanceThresholdAlarmQueue.removeAlarmForKey(UserPackage.of(userId, pkgName));
     }
 
     @GuardedBy("mLock")
@@ -959,7 +960,7 @@
                 mCurrentOngoingEvents.get(userId, pkgName);
         if (ongoingEvents == null || mIrs.isVip(userId, pkgName)) {
             // No ongoing transactions. No reason to schedule
-            mBalanceThresholdAlarmQueue.removeAlarmForKey(new Package(userId, pkgName));
+            mBalanceThresholdAlarmQueue.removeAlarmForKey(UserPackage.of(userId, pkgName));
             return;
         }
         mTrendCalculator.reset(getBalanceLocked(userId, pkgName),
@@ -972,7 +973,7 @@
         if (lowerTimeMs == TrendCalculator.WILL_NOT_CROSS_THRESHOLD) {
             if (upperTimeMs == TrendCalculator.WILL_NOT_CROSS_THRESHOLD) {
                 // Will never cross a threshold based on current events.
-                mBalanceThresholdAlarmQueue.removeAlarmForKey(new Package(userId, pkgName));
+                mBalanceThresholdAlarmQueue.removeAlarmForKey(UserPackage.of(userId, pkgName));
                 return;
             }
             timeToThresholdMs = upperTimeMs;
@@ -980,7 +981,7 @@
             timeToThresholdMs = (upperTimeMs == TrendCalculator.WILL_NOT_CROSS_THRESHOLD)
                     ? lowerTimeMs : Math.min(lowerTimeMs, upperTimeMs);
         }
-        mBalanceThresholdAlarmQueue.addAlarm(new Package(userId, pkgName),
+        mBalanceThresholdAlarmQueue.addAlarm(UserPackage.of(userId, pkgName),
                 SystemClock.elapsedRealtime() + timeToThresholdMs);
     }
 
@@ -1071,57 +1072,22 @@
 
     private final OngoingEventUpdater mOngoingEventUpdater = new OngoingEventUpdater();
 
-    private static final class Package {
-        public final String packageName;
-        public final int userId;
-
-        Package(int userId, String packageName) {
-            this.userId = userId;
-            this.packageName = packageName;
-        }
-
-        @Override
-        public String toString() {
-            return appToString(userId, packageName);
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            if (obj == null) {
-                return false;
-            }
-            if (this == obj) {
-                return true;
-            }
-            if (obj instanceof Package) {
-                Package other = (Package) obj;
-                return userId == other.userId && Objects.equals(packageName, other.packageName);
-            }
-            return false;
-        }
-
-        @Override
-        public int hashCode() {
-            return packageName.hashCode() + userId;
-        }
-    }
-
     /** Track when apps will cross the closest affordability threshold (in both directions). */
-    private class BalanceThresholdAlarmQueue extends AlarmQueue<Package> {
+    private class BalanceThresholdAlarmQueue extends AlarmQueue<UserPackage> {
         private BalanceThresholdAlarmQueue(Context context, Looper looper) {
             super(context, looper, ALARM_TAG_AFFORDABILITY_CHECK, "Affordability check", true,
                     15_000L);
         }
 
         @Override
-        protected boolean isForUser(@NonNull Package key, int userId) {
+        protected boolean isForUser(@NonNull UserPackage key, int userId) {
             return key.userId == userId;
         }
 
         @Override
-        protected void processExpiredAlarms(@NonNull ArraySet<Package> expired) {
+        protected void processExpiredAlarms(@NonNull ArraySet<UserPackage> expired) {
             for (int i = 0; i < expired.size(); ++i) {
-                Package p = expired.valueAt(i);
+                UserPackage p = expired.valueAt(i);
                 mHandler.obtainMessage(
                         MSG_CHECK_INDIVIDUAL_AFFORDABILITY, p.userId, 0, p.packageName)
                         .sendToTarget();
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java
index b426f16..46338fa 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/AlarmManagerEconomicPolicy.java
@@ -33,9 +33,10 @@
 import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_INEXACT_NONWAKEUP_CTP_CAKES;
 import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_INEXACT_WAKEUP_BASE_PRICE_CAKES;
 import static android.app.tare.EconomyManager.DEFAULT_AM_ACTION_ALARM_INEXACT_WAKEUP_CTP_CAKES;
-import static android.app.tare.EconomyManager.DEFAULT_AM_HARD_CONSUMPTION_LIMIT_CAKES;
 import static android.app.tare.EconomyManager.DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT_CAKES;
+import static android.app.tare.EconomyManager.DEFAULT_AM_MAX_CONSUMPTION_LIMIT_CAKES;
 import static android.app.tare.EconomyManager.DEFAULT_AM_MAX_SATIATED_BALANCE_CAKES;
+import static android.app.tare.EconomyManager.DEFAULT_AM_MIN_CONSUMPTION_LIMIT_CAKES;
 import static android.app.tare.EconomyManager.DEFAULT_AM_MIN_SATIATED_BALANCE_EXEMPTED_CAKES;
 import static android.app.tare.EconomyManager.DEFAULT_AM_MIN_SATIATED_BALANCE_OTHER_APP_CAKES;
 import static android.app.tare.EconomyManager.DEFAULT_AM_REWARD_NOTIFICATION_INTERACTION_INSTANT_CAKES;
@@ -71,9 +72,10 @@
 import static android.app.tare.EconomyManager.KEY_AM_ACTION_ALARM_INEXACT_NONWAKEUP_CTP;
 import static android.app.tare.EconomyManager.KEY_AM_ACTION_ALARM_INEXACT_WAKEUP_BASE_PRICE;
 import static android.app.tare.EconomyManager.KEY_AM_ACTION_ALARM_INEXACT_WAKEUP_CTP;
-import static android.app.tare.EconomyManager.KEY_AM_HARD_CONSUMPTION_LIMIT;
 import static android.app.tare.EconomyManager.KEY_AM_INITIAL_CONSUMPTION_LIMIT;
+import static android.app.tare.EconomyManager.KEY_AM_MAX_CONSUMPTION_LIMIT;
 import static android.app.tare.EconomyManager.KEY_AM_MAX_SATIATED_BALANCE;
+import static android.app.tare.EconomyManager.KEY_AM_MIN_CONSUMPTION_LIMIT;
 import static android.app.tare.EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_EXEMPTED;
 import static android.app.tare.EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_OTHER_APP;
 import static android.app.tare.EconomyManager.KEY_AM_REWARD_NOTIFICATION_INTERACTION_INSTANT;
@@ -146,7 +148,8 @@
     private long mMinSatiatedBalanceOther;
     private long mMaxSatiatedBalance;
     private long mInitialSatiatedConsumptionLimit;
-    private long mHardSatiatedConsumptionLimit;
+    private long mMinSatiatedConsumptionLimit;
+    private long mMaxSatiatedConsumptionLimit;
 
     private final KeyValueListParser mParser = new KeyValueListParser(',');
     private final Injector mInjector;
@@ -199,8 +202,13 @@
     }
 
     @Override
-    long getHardSatiatedConsumptionLimit() {
-        return mHardSatiatedConsumptionLimit;
+    long getMinSatiatedConsumptionLimit() {
+        return mMinSatiatedConsumptionLimit;
+    }
+
+    @Override
+    long getMaxSatiatedConsumptionLimit() {
+        return mMaxSatiatedConsumptionLimit;
     }
 
     @NonNull
@@ -240,12 +248,15 @@
         mMaxSatiatedBalance = getConstantAsCake(mParser, properties,
             KEY_AM_MAX_SATIATED_BALANCE, DEFAULT_AM_MAX_SATIATED_BALANCE_CAKES,
             Math.max(arcToCake(1), mMinSatiatedBalanceExempted));
+        mMinSatiatedConsumptionLimit = getConstantAsCake(mParser, properties,
+                KEY_AM_MIN_CONSUMPTION_LIMIT, DEFAULT_AM_MIN_CONSUMPTION_LIMIT_CAKES,
+                arcToCake(1));
         mInitialSatiatedConsumptionLimit = getConstantAsCake(mParser, properties,
-            KEY_AM_INITIAL_CONSUMPTION_LIMIT, DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT_CAKES,
-            arcToCake(1));
-        mHardSatiatedConsumptionLimit = getConstantAsCake(mParser, properties,
-            KEY_AM_HARD_CONSUMPTION_LIMIT, DEFAULT_AM_HARD_CONSUMPTION_LIMIT_CAKES,
-            mInitialSatiatedConsumptionLimit);
+                KEY_AM_INITIAL_CONSUMPTION_LIMIT, DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT_CAKES,
+                mMinSatiatedConsumptionLimit);
+        mMaxSatiatedConsumptionLimit = getConstantAsCake(mParser, properties,
+                KEY_AM_MAX_CONSUMPTION_LIMIT, DEFAULT_AM_MAX_CONSUMPTION_LIMIT_CAKES,
+                mInitialSatiatedConsumptionLimit);
 
         final long exactAllowWhileIdleWakeupBasePrice = getConstantAsCake(mParser, properties,
                 KEY_AM_ACTION_ALARM_ALLOW_WHILE_IDLE_EXACT_WAKEUP_BASE_PRICE,
@@ -396,9 +407,11 @@
         pw.decreaseIndent();
         pw.print("Max satiated balance", cakeToString(mMaxSatiatedBalance)).println();
         pw.print("Consumption limits: [");
+        pw.print(cakeToString(mMinSatiatedConsumptionLimit));
+        pw.print(", ");
         pw.print(cakeToString(mInitialSatiatedConsumptionLimit));
         pw.print(", ");
-        pw.print(cakeToString(mHardSatiatedConsumptionLimit));
+        pw.print(cakeToString(mMaxSatiatedConsumptionLimit));
         pw.println("]");
 
         pw.println();
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java
index 66f7c35..7a96076 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/CompleteEconomicPolicy.java
@@ -43,7 +43,8 @@
     private int mEnabledEconomicPolicyIds = 0;
     private int[] mCostModifiers = EmptyArray.INT;
     private long mInitialConsumptionLimit;
-    private long mHardConsumptionLimit;
+    private long mMinConsumptionLimit;
+    private long mMaxConsumptionLimit;
 
     CompleteEconomicPolicy(@NonNull InternalResourceService irs) {
         this(irs, new CompleteInjector());
@@ -100,14 +101,17 @@
 
     private void updateLimits() {
         long initialConsumptionLimit = 0;
-        long hardConsumptionLimit = 0;
+        long minConsumptionLimit = 0;
+        long maxConsumptionLimit = 0;
         for (int i = 0; i < mEnabledEconomicPolicies.size(); ++i) {
             final EconomicPolicy economicPolicy = mEnabledEconomicPolicies.valueAt(i);
             initialConsumptionLimit += economicPolicy.getInitialSatiatedConsumptionLimit();
-            hardConsumptionLimit += economicPolicy.getHardSatiatedConsumptionLimit();
+            minConsumptionLimit += economicPolicy.getMinSatiatedConsumptionLimit();
+            maxConsumptionLimit += economicPolicy.getMaxSatiatedConsumptionLimit();
         }
         mInitialConsumptionLimit = initialConsumptionLimit;
-        mHardConsumptionLimit = hardConsumptionLimit;
+        mMinConsumptionLimit = minConsumptionLimit;
+        mMaxConsumptionLimit = maxConsumptionLimit;
     }
 
     @Override
@@ -134,8 +138,13 @@
     }
 
     @Override
-    long getHardSatiatedConsumptionLimit() {
-        return mHardConsumptionLimit;
+    long getMinSatiatedConsumptionLimit() {
+        return mMinConsumptionLimit;
+    }
+
+    @Override
+    long getMaxSatiatedConsumptionLimit() {
+        return mMaxConsumptionLimit;
     }
 
     @NonNull
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java
index 008dcb8..b52f6f1 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/EconomicPolicy.java
@@ -232,15 +232,21 @@
      * Returns the maximum number of cakes that should be consumed during a full 100% discharge
      * cycle. This is the initial limit. The system may choose to increase the limit over time,
      * but the increased limit should never exceed the value returned from
-     * {@link #getHardSatiatedConsumptionLimit()}.
+     * {@link #getMaxSatiatedConsumptionLimit()}.
      */
     abstract long getInitialSatiatedConsumptionLimit();
 
     /**
-     * Returns the maximum number of cakes that should be consumed during a full 100% discharge
-     * cycle. This is the hard limit that should never be exceeded.
+     * Returns the minimum number of cakes that should be available for consumption during a full
+     * 100% discharge cycle.
      */
-    abstract long getHardSatiatedConsumptionLimit();
+    abstract long getMinSatiatedConsumptionLimit();
+
+    /**
+     * Returns the maximum number of cakes that should be available for consumption during a full
+     * 100% discharge cycle.
+     */
+    abstract long getMaxSatiatedConsumptionLimit();
 
     /** Return the set of modifiers that should apply to this policy's costs. */
     @NonNull
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
index dd0a194..581a545 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/InternalResourceService.java
@@ -670,7 +670,7 @@
         final long shortfall = (mCurrentBatteryLevel - QUANTITATIVE_EASING_BATTERY_THRESHOLD)
                 * currentConsumptionLimit / 100;
         final long newConsumptionLimit = Math.min(currentConsumptionLimit + shortfall,
-                mCompleteEconomicPolicy.getHardSatiatedConsumptionLimit());
+                mCompleteEconomicPolicy.getMaxSatiatedConsumptionLimit());
         if (newConsumptionLimit != currentConsumptionLimit) {
             Slog.i(TAG, "Increasing consumption limit from " + cakeToString(currentConsumptionLimit)
                     + " to " + cakeToString(newConsumptionLimit));
@@ -720,12 +720,12 @@
             // The stock is too low. We're doing pretty well. We can increase the stock slightly
             // to let apps do more work in the background.
             newConsumptionLimit = Math.min((long) (currentConsumptionLimit * 1.01),
-                    mCompleteEconomicPolicy.getHardSatiatedConsumptionLimit());
+                    mCompleteEconomicPolicy.getMaxSatiatedConsumptionLimit());
         } else if (percentageOfTarget < 100) {
             // The stock is too high IMO. We're below the target. Decrease the stock to reduce
             // background work.
             newConsumptionLimit = Math.max((long) (currentConsumptionLimit * .98),
-                    mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit());
+                    mCompleteEconomicPolicy.getMinSatiatedConsumptionLimit());
         } else {
             // The stock is just right.
             return;
@@ -957,9 +957,9 @@
             } else {
                 mScribe.loadFromDiskLocked();
                 if (mScribe.getSatiatedConsumptionLimitLocked()
-                        < mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit()
+                        < mCompleteEconomicPolicy.getMinSatiatedConsumptionLimit()
                         || mScribe.getSatiatedConsumptionLimitLocked()
-                        > mCompleteEconomicPolicy.getHardSatiatedConsumptionLimit()) {
+                        > mCompleteEconomicPolicy.getMaxSatiatedConsumptionLimit()) {
                     // Reset the consumption limit since several factors may have changed.
                     mScribe.setConsumptionLimitLocked(
                             mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit());
@@ -1442,17 +1442,16 @@
 
         private void updateEconomicPolicy() {
             synchronized (mLock) {
-                final long initialLimit =
-                        mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit();
-                final long hardLimit = mCompleteEconomicPolicy.getHardSatiatedConsumptionLimit();
+                final long minLimit = mCompleteEconomicPolicy.getMinSatiatedConsumptionLimit();
+                final long maxLimit = mCompleteEconomicPolicy.getMaxSatiatedConsumptionLimit();
                 final int oldEnabledPolicies = mCompleteEconomicPolicy.getEnabledPolicyIds();
                 mCompleteEconomicPolicy.tearDown();
                 mCompleteEconomicPolicy = new CompleteEconomicPolicy(InternalResourceService.this);
                 if (mIsEnabled && mBootPhase >= PHASE_THIRD_PARTY_APPS_CAN_START) {
                     mCompleteEconomicPolicy.setup(getAllDeviceConfigProperties());
-                    if (initialLimit != mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit()
-                            || hardLimit
-                            != mCompleteEconomicPolicy.getHardSatiatedConsumptionLimit()) {
+                    if (minLimit != mCompleteEconomicPolicy.getMinSatiatedConsumptionLimit()
+                            || maxLimit
+                            != mCompleteEconomicPolicy.getMaxSatiatedConsumptionLimit()) {
                         // Reset the consumption limit since several factors may have changed.
                         mScribe.setConsumptionLimitLocked(
                                 mCompleteEconomicPolicy.getInitialSatiatedConsumptionLimit());
diff --git a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
index 71c6d09..7cf459c 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/JobSchedulerEconomicPolicy.java
@@ -38,9 +38,10 @@
 import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_MIN_START_CTP_CAKES;
 import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_TIMEOUT_PENALTY_BASE_PRICE_CAKES;
 import static android.app.tare.EconomyManager.DEFAULT_JS_ACTION_JOB_TIMEOUT_PENALTY_CTP_CAKES;
-import static android.app.tare.EconomyManager.DEFAULT_JS_HARD_CONSUMPTION_LIMIT_CAKES;
 import static android.app.tare.EconomyManager.DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT_CAKES;
+import static android.app.tare.EconomyManager.DEFAULT_JS_MAX_CONSUMPTION_LIMIT_CAKES;
 import static android.app.tare.EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES;
+import static android.app.tare.EconomyManager.DEFAULT_JS_MIN_CONSUMPTION_LIMIT_CAKES;
 import static android.app.tare.EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_EXEMPTED_CAKES;
 import static android.app.tare.EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER_CAKES;
 import static android.app.tare.EconomyManager.DEFAULT_JS_MIN_SATIATED_BALANCE_OTHER_APP_CAKES;
@@ -84,9 +85,10 @@
 import static android.app.tare.EconomyManager.KEY_JS_ACTION_JOB_MIN_START_CTP;
 import static android.app.tare.EconomyManager.KEY_JS_ACTION_JOB_TIMEOUT_PENALTY_BASE_PRICE;
 import static android.app.tare.EconomyManager.KEY_JS_ACTION_JOB_TIMEOUT_PENALTY_CTP;
-import static android.app.tare.EconomyManager.KEY_JS_HARD_CONSUMPTION_LIMIT;
 import static android.app.tare.EconomyManager.KEY_JS_INITIAL_CONSUMPTION_LIMIT;
+import static android.app.tare.EconomyManager.KEY_JS_MAX_CONSUMPTION_LIMIT;
 import static android.app.tare.EconomyManager.KEY_JS_MAX_SATIATED_BALANCE;
+import static android.app.tare.EconomyManager.KEY_JS_MIN_CONSUMPTION_LIMIT;
 import static android.app.tare.EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED;
 import static android.app.tare.EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_INCREMENT_APP_UPDATER;
 import static android.app.tare.EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP;
@@ -159,7 +161,8 @@
     private long mMinSatiatedBalanceIncrementalAppUpdater;
     private long mMaxSatiatedBalance;
     private long mInitialSatiatedConsumptionLimit;
-    private long mHardSatiatedConsumptionLimit;
+    private long mMinSatiatedConsumptionLimit;
+    private long mMaxSatiatedConsumptionLimit;
 
     private final KeyValueListParser mParser = new KeyValueListParser(',');
     private final Injector mInjector;
@@ -216,8 +219,13 @@
     }
 
     @Override
-    long getHardSatiatedConsumptionLimit() {
-        return mHardSatiatedConsumptionLimit;
+    long getMinSatiatedConsumptionLimit() {
+        return mMinSatiatedConsumptionLimit;
+    }
+
+    @Override
+    long getMaxSatiatedConsumptionLimit() {
+        return mMaxSatiatedConsumptionLimit;
     }
 
     @NonNull
@@ -260,12 +268,15 @@
         mMaxSatiatedBalance = getConstantAsCake(mParser, properties,
             KEY_JS_MAX_SATIATED_BALANCE, DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES,
             Math.max(arcToCake(1), mMinSatiatedBalanceExempted));
+        mMinSatiatedConsumptionLimit = getConstantAsCake(mParser, properties,
+                KEY_JS_MIN_CONSUMPTION_LIMIT, DEFAULT_JS_MIN_CONSUMPTION_LIMIT_CAKES,
+                arcToCake(1));
         mInitialSatiatedConsumptionLimit = getConstantAsCake(mParser, properties,
-            KEY_JS_INITIAL_CONSUMPTION_LIMIT, DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT_CAKES,
-            arcToCake(1));
-        mHardSatiatedConsumptionLimit = getConstantAsCake(mParser, properties,
-            KEY_JS_HARD_CONSUMPTION_LIMIT, DEFAULT_JS_HARD_CONSUMPTION_LIMIT_CAKES,
-            mInitialSatiatedConsumptionLimit);
+                KEY_JS_INITIAL_CONSUMPTION_LIMIT, DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT_CAKES,
+                mMinSatiatedConsumptionLimit);
+        mMaxSatiatedConsumptionLimit = getConstantAsCake(mParser, properties,
+                KEY_JS_MAX_CONSUMPTION_LIMIT, DEFAULT_JS_MAX_CONSUMPTION_LIMIT_CAKES,
+                mInitialSatiatedConsumptionLimit);
 
         mActions.put(ACTION_JOB_MAX_START, new Action(ACTION_JOB_MAX_START,
                 getConstantAsCake(mParser, properties,
@@ -420,9 +431,11 @@
         pw.decreaseIndent();
         pw.print("Max satiated balance", cakeToString(mMaxSatiatedBalance)).println();
         pw.print("Consumption limits: [");
+        pw.print(cakeToString(mMinSatiatedConsumptionLimit));
+        pw.print(", ");
         pw.print(cakeToString(mInitialSatiatedConsumptionLimit));
         pw.print(", ");
-        pw.print(cakeToString(mHardSatiatedConsumptionLimit));
+        pw.print(cakeToString(mMaxSatiatedConsumptionLimit));
         pw.println("]");
 
         pw.println();
diff --git a/cmds/uiautomator/OWNERS b/cmds/uiautomator/OWNERS
new file mode 100644
index 0000000..5c7f452
--- /dev/null
+++ b/cmds/uiautomator/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 833089
+peykov@google.com
+normancheung@google.com
+guran@google.com
diff --git a/cmds/uiautomator/cmds/uiautomator/src/com/android/commands/uiautomator/DumpCommand.java b/cmds/uiautomator/cmds/uiautomator/src/com/android/commands/uiautomator/DumpCommand.java
index 3b14be7..24727c5 100644
--- a/cmds/uiautomator/cmds/uiautomator/src/com/android/commands/uiautomator/DumpCommand.java
+++ b/cmds/uiautomator/cmds/uiautomator/src/com/android/commands/uiautomator/DumpCommand.java
@@ -107,7 +107,7 @@
                         DisplayManagerGlobal.getInstance().getRealDisplay(Display.DEFAULT_DISPLAY);
                 int rotation = display.getRotation();
                 Point size = new Point();
-                display.getSize(size);
+                display.getRealSize(size);
                 AccessibilityNodeInfoDumper.dumpWindowToFile(info, dumpFile, rotation, size.x,
                         size.y);
             }
diff --git a/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java
index ab198b3..488292d 100644
--- a/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java
+++ b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/AccessibilityNodeInfoDumper.java
@@ -139,7 +139,7 @@
                 serializer.attribute("", "id", Integer.toString(displayId));
                 int rotation = display.getRotation();
                 Point size = new Point();
-                display.getSize(size);
+                display.getRealSize(size);
                 for (int i = 0, n = windows.size(); i < n; ++i) {
                     dumpWindowRec(windows.get(i), serializer, i, size.x, size.y, rotation);
                 }
diff --git a/cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiDevice.java b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiDevice.java
index 6fd2bf2..1bcd343e 100644
--- a/cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiDevice.java
+++ b/cmds/uiautomator/library/core-src/com/android/uiautomator/core/UiDevice.java
@@ -767,7 +767,7 @@
         if(root != null) {
             Display display = getAutomatorBridge().getDefaultDisplay();
             Point size = new Point();
-            display.getSize(size);
+            display.getRealSize(size);
             AccessibilityNodeInfoDumper.dumpWindowToFile(root,
                     new File(new File(Environment.getDataDirectory(), "local/tmp"), fileName),
                     display.getRotation(), size.x, size.y);
diff --git a/core/api/current.txt b/core/api/current.txt
index eef2324..e2f2fae 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -143,6 +143,7 @@
     field public static final String READ_MEDIA_AUDIO = "android.permission.READ_MEDIA_AUDIO";
     field public static final String READ_MEDIA_IMAGES = "android.permission.READ_MEDIA_IMAGES";
     field public static final String READ_MEDIA_VIDEO = "android.permission.READ_MEDIA_VIDEO";
+    field public static final String READ_MEDIA_VISUAL_USER_SELECTED = "android.permission.READ_MEDIA_VISUAL_USER_SELECTED";
     field public static final String READ_NEARBY_STREAMING_POLICY = "android.permission.READ_NEARBY_STREAMING_POLICY";
     field public static final String READ_PHONE_NUMBERS = "android.permission.READ_PHONE_NUMBERS";
     field public static final String READ_PHONE_STATE = "android.permission.READ_PHONE_STATE";
@@ -172,6 +173,7 @@
     field public static final String REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE = "android.permission.REQUEST_OBSERVE_COMPANION_DEVICE_PRESENCE";
     field public static final String REQUEST_PASSWORD_COMPLEXITY = "android.permission.REQUEST_PASSWORD_COMPLEXITY";
     field @Deprecated public static final String RESTART_PACKAGES = "android.permission.RESTART_PACKAGES";
+    field public static final String RUN_LONG_JOBS = "android.permission.RUN_LONG_JOBS";
     field public static final String SCHEDULE_EXACT_ALARM = "android.permission.SCHEDULE_EXACT_ALARM";
     field public static final String SEND_RESPOND_VIA_MESSAGE = "android.permission.SEND_RESPOND_VIA_MESSAGE";
     field public static final String SEND_SMS = "android.permission.SEND_SMS";
@@ -4603,23 +4605,24 @@
 
   public class AlarmManager {
     method public boolean canScheduleExactAlarms();
-    method public void cancel(android.app.PendingIntent);
-    method public void cancel(android.app.AlarmManager.OnAlarmListener);
+    method public void cancel(@NonNull android.app.PendingIntent);
+    method public void cancel(@NonNull android.app.AlarmManager.OnAlarmListener);
     method public void cancelAll();
     method public android.app.AlarmManager.AlarmClockInfo getNextAlarmClock();
-    method public void set(int, long, android.app.PendingIntent);
-    method public void set(int, long, String, android.app.AlarmManager.OnAlarmListener, android.os.Handler);
-    method @RequiresPermission(android.Manifest.permission.SCHEDULE_EXACT_ALARM) public void setAlarmClock(android.app.AlarmManager.AlarmClockInfo, android.app.PendingIntent);
-    method public void setAndAllowWhileIdle(int, long, android.app.PendingIntent);
-    method @RequiresPermission(value=android.Manifest.permission.SCHEDULE_EXACT_ALARM, conditional=true) public void setExact(int, long, android.app.PendingIntent);
-    method @RequiresPermission(value=android.Manifest.permission.SCHEDULE_EXACT_ALARM, conditional=true) public void setExact(int, long, String, android.app.AlarmManager.OnAlarmListener, android.os.Handler);
-    method @RequiresPermission(value=android.Manifest.permission.SCHEDULE_EXACT_ALARM, conditional=true) public void setExactAndAllowWhileIdle(int, long, android.app.PendingIntent);
-    method public void setInexactRepeating(int, long, long, android.app.PendingIntent);
-    method public void setRepeating(int, long, long, android.app.PendingIntent);
+    method public void set(int, long, @NonNull android.app.PendingIntent);
+    method public void set(int, long, @Nullable String, @NonNull android.app.AlarmManager.OnAlarmListener, @Nullable android.os.Handler);
+    method @RequiresPermission(android.Manifest.permission.SCHEDULE_EXACT_ALARM) public void setAlarmClock(@NonNull android.app.AlarmManager.AlarmClockInfo, @NonNull android.app.PendingIntent);
+    method public void setAndAllowWhileIdle(int, long, @NonNull android.app.PendingIntent);
+    method @RequiresPermission(value=android.Manifest.permission.SCHEDULE_EXACT_ALARM, conditional=true) public void setExact(int, long, @NonNull android.app.PendingIntent);
+    method @RequiresPermission(value=android.Manifest.permission.SCHEDULE_EXACT_ALARM, conditional=true) public void setExact(int, long, @Nullable String, @NonNull android.app.AlarmManager.OnAlarmListener, @Nullable android.os.Handler);
+    method @RequiresPermission(value=android.Manifest.permission.SCHEDULE_EXACT_ALARM, conditional=true) public void setExactAndAllowWhileIdle(int, long, @NonNull android.app.PendingIntent);
+    method public void setInexactRepeating(int, long, long, @NonNull android.app.PendingIntent);
+    method public void setRepeating(int, long, long, @NonNull android.app.PendingIntent);
     method @RequiresPermission(android.Manifest.permission.SET_TIME) public void setTime(long);
     method @RequiresPermission(android.Manifest.permission.SET_TIME_ZONE) public void setTimeZone(String);
-    method public void setWindow(int, long, long, android.app.PendingIntent);
-    method public void setWindow(int, long, long, String, android.app.AlarmManager.OnAlarmListener, android.os.Handler);
+    method public void setWindow(int, long, long, @NonNull android.app.PendingIntent);
+    method public void setWindow(int, long, long, @Nullable String, @NonNull android.app.AlarmManager.OnAlarmListener, @Nullable android.os.Handler);
+    method public void setWindow(int, long, long, @Nullable String, @NonNull java.util.concurrent.Executor, @NonNull android.app.AlarmManager.OnAlarmListener);
     field public static final String ACTION_NEXT_ALARM_CLOCK_CHANGED = "android.app.action.NEXT_ALARM_CLOCK_CHANGED";
     field public static final String ACTION_SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED = "android.app.action.SCHEDULE_EXACT_ALARM_PERMISSION_STATE_CHANGED";
     field public static final int ELAPSED_REALTIME = 3; // 0x3
@@ -7456,6 +7459,7 @@
     method public long getMaximumTimeToLock(@Nullable android.content.ComponentName);
     method @NonNull public java.util.List<java.lang.String> getMeteredDataDisabledPackages(@NonNull android.content.ComponentName);
     method public int getMinimumRequiredWifiSecurityLevel();
+    method public int getMtePolicy();
     method @RequiresPermission(value=android.Manifest.permission.READ_NEARBY_STREAMING_POLICY, conditional=true) public int getNearbyAppStreamingPolicy();
     method @RequiresPermission(value=android.Manifest.permission.READ_NEARBY_STREAMING_POLICY, conditional=true) public int getNearbyNotificationStreamingPolicy();
     method @Deprecated @ColorInt public int getOrganizationColor(@NonNull android.content.ComponentName);
@@ -7604,6 +7608,7 @@
     method public void setMaximumTimeToLock(@NonNull android.content.ComponentName, long);
     method @NonNull public java.util.List<java.lang.String> setMeteredDataDisabledPackages(@NonNull android.content.ComponentName, @NonNull java.util.List<java.lang.String>);
     method public void setMinimumRequiredWifiSecurityLevel(int);
+    method public void setMtePolicy(int);
     method public void setNearbyAppStreamingPolicy(int);
     method public void setNearbyNotificationStreamingPolicy(int);
     method public void setNetworkLoggingEnabled(@Nullable android.content.ComponentName, boolean);
@@ -7787,6 +7792,9 @@
     field public static final int LOCK_TASK_FEATURE_SYSTEM_INFO = 1; // 0x1
     field public static final int MAKE_USER_EPHEMERAL = 2; // 0x2
     field public static final String MIME_TYPE_PROVISIONING_NFC = "application/com.android.managedprovisioning";
+    field public static final int MTE_DISABLED = 2; // 0x2
+    field public static final int MTE_ENABLED = 1; // 0x1
+    field public static final int MTE_NOT_CONTROLLED_BY_POLICY = 0; // 0x0
     field public static final int NEARBY_STREAMING_DISABLED = 1; // 0x1
     field public static final int NEARBY_STREAMING_ENABLED = 2; // 0x2
     field public static final int NEARBY_STREAMING_NOT_CONTROLLED_BY_POLICY = 0; // 0x0
@@ -8948,9 +8956,18 @@
 
 package android.companion {
 
+  public final class AssociatedDevice implements android.os.Parcelable {
+    method public int describeContents();
+    method @Nullable public android.bluetooth.le.ScanResult getBleDevice();
+    method @Nullable public android.bluetooth.BluetoothDevice getBluetoothDevice();
+    method @Nullable public android.net.wifi.ScanResult getWifiDevice();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.companion.AssociatedDevice> CREATOR;
+  }
+
   public final class AssociationInfo implements android.os.Parcelable {
     method public int describeContents();
-    method @Nullable public android.os.Parcelable getAssociatedDevice();
+    method @Nullable public android.companion.AssociatedDevice getAssociatedDevice();
     method @Nullable public android.net.MacAddress getDeviceMacAddress();
     method @Nullable public String getDeviceProfile();
     method @Nullable public CharSequence getDisplayName();
@@ -11648,7 +11665,7 @@
   public static final class PackageInstaller.PreapprovalDetails implements android.os.Parcelable {
     method public int describeContents();
     method @Nullable public android.graphics.Bitmap getIcon();
-    method @NonNull public String getLabel();
+    method @NonNull public CharSequence getLabel();
     method @NonNull public android.icu.util.ULocale getLocale();
     method @NonNull public String getPackageName();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -11659,7 +11676,7 @@
     ctor public PackageInstaller.PreapprovalDetails.Builder();
     method @NonNull public android.content.pm.PackageInstaller.PreapprovalDetails build();
     method @NonNull public android.content.pm.PackageInstaller.PreapprovalDetails.Builder setIcon(@NonNull android.graphics.Bitmap);
-    method @NonNull public android.content.pm.PackageInstaller.PreapprovalDetails.Builder setLabel(@NonNull String);
+    method @NonNull public android.content.pm.PackageInstaller.PreapprovalDetails.Builder setLabel(@NonNull CharSequence);
     method @NonNull public android.content.pm.PackageInstaller.PreapprovalDetails.Builder setLocale(@NonNull android.icu.util.ULocale);
     method @NonNull public android.content.pm.PackageInstaller.PreapprovalDetails.Builder setPackageName(@NonNull String);
   }
@@ -18417,7 +18434,7 @@
     method public android.graphics.Point getLeftEyePosition();
     method public android.graphics.Point getMouthPosition();
     method public android.graphics.Point getRightEyePosition();
-    method public int getScore();
+    method @IntRange(from=android.hardware.camera2.params.Face.SCORE_MIN, to=android.hardware.camera2.params.Face.SCORE_MAX) public int getScore();
     field public static final int ID_UNSUPPORTED = -1; // 0xffffffff
     field public static final int SCORE_MAX = 100; // 0x64
     field public static final int SCORE_MIN = 1; // 0x1
@@ -18432,7 +18449,7 @@
     method @NonNull public android.hardware.camera2.params.Face.Builder setLeftEyePosition(@NonNull android.graphics.Point);
     method @NonNull public android.hardware.camera2.params.Face.Builder setMouthPosition(@NonNull android.graphics.Point);
     method @NonNull public android.hardware.camera2.params.Face.Builder setRightEyePosition(@NonNull android.graphics.Point);
-    method @NonNull public android.hardware.camera2.params.Face.Builder setScore(int);
+    method @NonNull public android.hardware.camera2.params.Face.Builder setScore(@IntRange(from=android.hardware.camera2.params.Face.SCORE_MIN, to=android.hardware.camera2.params.Face.SCORE_MAX) int);
   }
 
   public final class InputConfiguration {
@@ -19473,6 +19490,7 @@
 
   public final class GnssCapabilities implements android.os.Parcelable {
     method public int describeContents();
+    method @NonNull public java.util.List<android.location.GnssSignalType> getGnssSignalTypes();
     method public boolean hasAntennaInfo();
     method public boolean hasGeofencing();
     method @Deprecated public boolean hasGnssAntennaInfo();
@@ -19506,6 +19524,7 @@
     ctor public GnssCapabilities.Builder();
     ctor public GnssCapabilities.Builder(@NonNull android.location.GnssCapabilities);
     method @NonNull public android.location.GnssCapabilities build();
+    method @NonNull public android.location.GnssCapabilities.Builder setGnssSignalTypes(@NonNull java.util.List<android.location.GnssSignalType>);
     method @NonNull public android.location.GnssCapabilities.Builder setHasAntennaInfo(boolean);
     method @NonNull public android.location.GnssCapabilities.Builder setHasGeofencing(boolean);
     method @NonNull public android.location.GnssCapabilities.Builder setHasLowPowerMode(boolean);
@@ -19718,6 +19737,16 @@
     field @Deprecated public static final int STATUS_READY = 1; // 0x1
   }
 
+  public final class GnssSignalType implements android.os.Parcelable {
+    method @NonNull public static android.location.GnssSignalType create(int, @FloatRange(from=0.0f, fromInclusive=false) double, @NonNull String);
+    method public int describeContents();
+    method @FloatRange(from=0.0f, fromInclusive=false) public double getCarrierFrequencyHz();
+    method @NonNull public String getCodeType();
+    method public int getConstellationType();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.location.GnssSignalType> CREATOR;
+  }
+
   public final class GnssStatus implements android.os.Parcelable {
     method public int describeContents();
     method @FloatRange(from=0, to=360) public float getAzimuthDegrees(@IntRange(from=0) int);
@@ -32539,6 +32568,7 @@
     field public static final String DISALLOW_BLUETOOTH = "no_bluetooth";
     field public static final String DISALLOW_BLUETOOTH_SHARING = "no_bluetooth_sharing";
     field public static final String DISALLOW_CAMERA_TOGGLE = "disallow_camera_toggle";
+    field public static final String DISALLOW_CELLULAR_2G = "no_cellular_2g";
     field public static final String DISALLOW_CHANGE_WIFI_STATE = "no_change_wifi_state";
     field public static final String DISALLOW_CONFIG_BLUETOOTH = "no_config_bluetooth";
     field public static final String DISALLOW_CONFIG_BRIGHTNESS = "no_config_brightness";
@@ -41585,7 +41615,7 @@
     field public static final String KEY_CARRIER_CONFIG_APPLIED_BOOL = "carrier_config_applied_bool";
     field public static final String KEY_CARRIER_CONFIG_VERSION_STRING = "carrier_config_version_string";
     field public static final String KEY_CARRIER_CROSS_SIM_IMS_AVAILABLE_BOOL = "carrier_cross_sim_ims_available_bool";
-    field public static final String KEY_CARRIER_DATA_CALL_PERMANENT_FAILURE_STRINGS = "carrier_data_call_permanent_failure_strings";
+    field @Deprecated public static final String KEY_CARRIER_DATA_CALL_PERMANENT_FAILURE_STRINGS = "carrier_data_call_permanent_failure_strings";
     field public static final String KEY_CARRIER_DEFAULT_ACTIONS_ON_DCFAILURE_STRING_ARRAY = "carrier_default_actions_on_dcfailure_string_array";
     field public static final String KEY_CARRIER_DEFAULT_ACTIONS_ON_DEFAULT_NETWORK_AVAILABLE = "carrier_default_actions_on_default_network_available_string_array";
     field public static final String KEY_CARRIER_DEFAULT_ACTIONS_ON_REDIRECTION_STRING_ARRAY = "carrier_default_actions_on_redirection_string_array";
@@ -41722,6 +41752,7 @@
     field public static final String KEY_MMS_MMS_READ_REPORT_ENABLED_BOOL = "enableMMSReadReports";
     field public static final String KEY_MMS_MULTIPART_SMS_ENABLED_BOOL = "enableMultipartSMS";
     field public static final String KEY_MMS_NAI_SUFFIX_STRING = "naiSuffix";
+    field public static final String KEY_MMS_NETWORK_RELEASE_TIMEOUT_MILLIS_INT = "mms_network_release_timeout_millis_int";
     field public static final String KEY_MMS_NOTIFY_WAP_MMSC_ENABLED_BOOL = "enabledNotifyWapMMSC";
     field public static final String KEY_MMS_RECIPIENT_LIMIT_INT = "recipientLimit";
     field public static final String KEY_MMS_SEND_MULTIPART_SMS_AS_SEPARATE_MESSAGES_BOOL = "sendMultipartSmsAsSeparateMessages";
@@ -41753,6 +41784,7 @@
     field public static final String KEY_PING_TEST_BEFORE_DATA_SWITCH_BOOL = "ping_test_before_data_switch_bool";
     field public static final String KEY_PREFER_2G_BOOL = "prefer_2g_bool";
     field public static final String KEY_PREMIUM_CAPABILITY_MAXIMUM_NOTIFICATION_COUNT_INT_ARRAY = "premium_capability_maximum_notification_count_int_array";
+    field public static final String KEY_PREMIUM_CAPABILITY_NETWORK_SETUP_TIME_MILLIS_LONG = "premium_capability_network_setup_time_millis_long";
     field public static final String KEY_PREMIUM_CAPABILITY_NOTIFICATION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG = "premium_capability_notification_backoff_hysteresis_time_millis_long";
     field public static final String KEY_PREMIUM_CAPABILITY_NOTIFICATION_DISPLAY_TIMEOUT_MILLIS_LONG = "premium_capability_notification_display_timeout_millis_long";
     field public static final String KEY_PREMIUM_CAPABILITY_PURCHASE_CONDITION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG = "premium_capability_purchase_condition_backoff_hysteresis_time_millis_long";
@@ -43410,14 +43442,18 @@
     field public static final int RESULT_RECEIVE_WHILE_ENCRYPTED = 504; // 0x1f8
     field public static final int RESULT_REMOTE_EXCEPTION = 31; // 0x1f
     field public static final int RESULT_REQUEST_NOT_SUPPORTED = 24; // 0x18
+    field public static final int RESULT_RIL_ABORTED = 137; // 0x89
     field public static final int RESULT_RIL_ACCESS_BARRED = 122; // 0x7a
     field public static final int RESULT_RIL_BLOCKED_DUE_TO_CALL = 123; // 0x7b
     field public static final int RESULT_RIL_CANCELLED = 119; // 0x77
+    field public static final int RESULT_RIL_DEVICE_IN_USE = 136; // 0x88
     field public static final int RESULT_RIL_ENCODING_ERR = 109; // 0x6d
     field public static final int RESULT_RIL_GENERIC_ERROR = 124; // 0x7c
     field public static final int RESULT_RIL_INTERNAL_ERR = 113; // 0x71
     field public static final int RESULT_RIL_INVALID_ARGUMENTS = 104; // 0x68
     field public static final int RESULT_RIL_INVALID_MODEM_STATE = 115; // 0x73
+    field public static final int RESULT_RIL_INVALID_RESPONSE = 125; // 0x7d
+    field public static final int RESULT_RIL_INVALID_SIM_STATE = 130; // 0x82
     field public static final int RESULT_RIL_INVALID_SMSC_ADDRESS = 110; // 0x6e
     field public static final int RESULT_RIL_INVALID_SMS_FORMAT = 107; // 0x6b
     field public static final int RESULT_RIL_INVALID_STATE = 103; // 0x67
@@ -43426,14 +43462,23 @@
     field public static final int RESULT_RIL_NETWORK_NOT_READY = 116; // 0x74
     field public static final int RESULT_RIL_NETWORK_REJECT = 102; // 0x66
     field public static final int RESULT_RIL_NO_MEMORY = 105; // 0x69
+    field public static final int RESULT_RIL_NO_NETWORK_FOUND = 135; // 0x87
     field public static final int RESULT_RIL_NO_RESOURCES = 118; // 0x76
+    field public static final int RESULT_RIL_NO_SMS_TO_ACK = 131; // 0x83
+    field public static final int RESULT_RIL_NO_SUBSCRIPTION = 134; // 0x86
     field public static final int RESULT_RIL_OPERATION_NOT_ALLOWED = 117; // 0x75
     field public static final int RESULT_RIL_RADIO_NOT_AVAILABLE = 100; // 0x64
     field public static final int RESULT_RIL_REQUEST_NOT_SUPPORTED = 114; // 0x72
     field public static final int RESULT_RIL_REQUEST_RATE_LIMITED = 106; // 0x6a
     field public static final int RESULT_RIL_SIMULTANEOUS_SMS_AND_CALL_NOT_ALLOWED = 121; // 0x79
     field public static final int RESULT_RIL_SIM_ABSENT = 120; // 0x78
+    field public static final int RESULT_RIL_SIM_BUSY = 132; // 0x84
+    field public static final int RESULT_RIL_SIM_ERROR = 129; // 0x81
+    field public static final int RESULT_RIL_SIM_FULL = 133; // 0x85
+    field public static final int RESULT_RIL_SIM_PIN2 = 126; // 0x7e
+    field public static final int RESULT_RIL_SIM_PUK2 = 127; // 0x7f
     field public static final int RESULT_RIL_SMS_SEND_FAIL_RETRY = 101; // 0x65
+    field public static final int RESULT_RIL_SUBSCRIPTION_NOT_AVAILABLE = 128; // 0x80
     field public static final int RESULT_RIL_SYSTEM_ERR = 108; // 0x6c
     field public static final int RESULT_SMS_BLOCKED_DURING_EMERGENCY = 29; // 0x1d
     field public static final int RESULT_SMS_SEND_RETRY_FAILED = 30; // 0x1e
@@ -44006,9 +44051,10 @@
     field public static final long NETWORK_TYPE_BITMASK_HSPA = 512L; // 0x200L
     field public static final long NETWORK_TYPE_BITMASK_HSPAP = 16384L; // 0x4000L
     field public static final long NETWORK_TYPE_BITMASK_HSUPA = 256L; // 0x100L
+    field public static final long NETWORK_TYPE_BITMASK_IDEN = 1024L; // 0x400L
     field public static final long NETWORK_TYPE_BITMASK_IWLAN = 131072L; // 0x20000L
     field public static final long NETWORK_TYPE_BITMASK_LTE = 4096L; // 0x1000L
-    field public static final long NETWORK_TYPE_BITMASK_LTE_CA = 262144L; // 0x40000L
+    field @Deprecated public static final long NETWORK_TYPE_BITMASK_LTE_CA = 262144L; // 0x40000L
     field public static final long NETWORK_TYPE_BITMASK_NR = 524288L; // 0x80000L
     field public static final long NETWORK_TYPE_BITMASK_TD_SCDMA = 65536L; // 0x10000L
     field public static final long NETWORK_TYPE_BITMASK_UMTS = 4L; // 0x4L
@@ -44046,6 +44092,7 @@
     field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_NOT_AVAILABLE = 12; // 0xc
     field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA = 14; // 0xe
     field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_OVERRIDDEN = 5; // 0x5
+    field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP = 15; // 0xf
     field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_REQUEST_FAILED = 11; // 0xb
     field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_SUCCESS = 1; // 0x1
     field public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_THROTTLED = 2; // 0x2
@@ -45561,6 +45608,14 @@
     field public static final int DONE = -1; // 0xffffffff
   }
 
+  public static class SegmentFinder.DefaultSegmentFinder extends android.text.SegmentFinder {
+    ctor public SegmentFinder.DefaultSegmentFinder(@NonNull int[]);
+    method public int nextEndBoundary(@IntRange(from=0) int);
+    method public int nextStartBoundary(@IntRange(from=0) int);
+    method public int previousEndBoundary(@IntRange(from=0) int);
+    method public int previousStartBoundary(@IntRange(from=0) int);
+  }
+
   public class Selection {
     method public static boolean extendDown(android.text.Spannable, android.text.Layout);
     method public static boolean extendLeft(android.text.Spannable, android.text.Layout);
@@ -48550,6 +48605,12 @@
     field public static final int VERTICAL_GRAVITY_MASK = 112; // 0x70
   }
 
+  public class HandwritingDelegateConfiguration {
+    ctor public HandwritingDelegateConfiguration(@IdRes int, @NonNull Runnable);
+    method public int getDelegatorViewId();
+    method @NonNull public Runnable getInitiationCallback();
+  }
+
   public class HapticFeedbackConstants {
     field public static final int CLOCK_TICK = 4; // 0x4
     field public static final int CONFIRM = 16; // 0x10
@@ -49567,16 +49628,13 @@
   }
 
   public final class PixelCopy {
-    method @NonNull public static android.view.PixelCopy.Request ofSurface(@NonNull android.view.Surface, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.view.PixelCopy.CopyResult>);
-    method @NonNull public static android.view.PixelCopy.Request ofSurface(@NonNull android.view.SurfaceView, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.view.PixelCopy.CopyResult>);
-    method @NonNull public static android.view.PixelCopy.Request ofWindow(@NonNull android.view.Window, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.view.PixelCopy.CopyResult>);
-    method @NonNull public static android.view.PixelCopy.Request ofWindow(@NonNull android.view.View, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.view.PixelCopy.CopyResult>);
     method public static void request(@NonNull android.view.SurfaceView, @NonNull android.graphics.Bitmap, @NonNull android.view.PixelCopy.OnPixelCopyFinishedListener, @NonNull android.os.Handler);
     method public static void request(@NonNull android.view.SurfaceView, @Nullable android.graphics.Rect, @NonNull android.graphics.Bitmap, @NonNull android.view.PixelCopy.OnPixelCopyFinishedListener, @NonNull android.os.Handler);
     method public static void request(@NonNull android.view.Surface, @NonNull android.graphics.Bitmap, @NonNull android.view.PixelCopy.OnPixelCopyFinishedListener, @NonNull android.os.Handler);
     method public static void request(@NonNull android.view.Surface, @Nullable android.graphics.Rect, @NonNull android.graphics.Bitmap, @NonNull android.view.PixelCopy.OnPixelCopyFinishedListener, @NonNull android.os.Handler);
     method public static void request(@NonNull android.view.Window, @NonNull android.graphics.Bitmap, @NonNull android.view.PixelCopy.OnPixelCopyFinishedListener, @NonNull android.os.Handler);
     method public static void request(@NonNull android.view.Window, @Nullable android.graphics.Rect, @NonNull android.graphics.Bitmap, @NonNull android.view.PixelCopy.OnPixelCopyFinishedListener, @NonNull android.os.Handler);
+    method public static void request(@NonNull android.view.PixelCopy.Request);
     field public static final int ERROR_DESTINATION_INVALID = 5; // 0x5
     field public static final int ERROR_SOURCE_INVALID = 4; // 0x4
     field public static final int ERROR_SOURCE_NO_DATA = 3; // 0x3
@@ -49585,19 +49643,28 @@
     field public static final int SUCCESS = 0; // 0x0
   }
 
-  public static final class PixelCopy.CopyResult {
-    method @NonNull public android.graphics.Bitmap getBitmap();
-    method public int getStatus();
-  }
-
   public static interface PixelCopy.OnPixelCopyFinishedListener {
     method public void onPixelCopyFinished(int);
   }
 
   public static final class PixelCopy.Request {
-    method public void request();
-    method @NonNull public android.view.PixelCopy.Request setDestinationBitmap(@Nullable android.graphics.Bitmap);
-    method @NonNull public android.view.PixelCopy.Request setSourceRect(@Nullable android.graphics.Rect);
+    method @Nullable public android.graphics.Bitmap getDestinationBitmap();
+    method @Nullable public android.graphics.Rect getSourceRect();
+    method @NonNull public static android.view.PixelCopy.Request.Builder ofSurface(@NonNull android.view.Surface, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.view.PixelCopy.Result>);
+    method @NonNull public static android.view.PixelCopy.Request.Builder ofSurface(@NonNull android.view.SurfaceView, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.view.PixelCopy.Result>);
+    method @NonNull public static android.view.PixelCopy.Request.Builder ofWindow(@NonNull android.view.Window, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.view.PixelCopy.Result>);
+    method @NonNull public static android.view.PixelCopy.Request.Builder ofWindow(@NonNull android.view.View, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.view.PixelCopy.Result>);
+  }
+
+  public static final class PixelCopy.Request.Builder {
+    method @NonNull public android.view.PixelCopy.Request build();
+    method @NonNull public android.view.PixelCopy.Request.Builder setDestinationBitmap(@Nullable android.graphics.Bitmap);
+    method @NonNull public android.view.PixelCopy.Request.Builder setSourceRect(@Nullable android.graphics.Rect);
+  }
+
+  public static final class PixelCopy.Result {
+    method @NonNull public android.graphics.Bitmap getBitmap();
+    method public int getStatus();
   }
 
   public final class PointerIcon implements android.os.Parcelable {
@@ -50045,7 +50112,7 @@
     method public void dispatchCreateViewTranslationRequest(@NonNull java.util.Map<android.view.autofill.AutofillId,long[]>, @NonNull int[], @NonNull android.view.translation.TranslationCapability, @NonNull java.util.List<android.view.translation.ViewTranslationRequest>);
     method public void dispatchDisplayHint(int);
     method public boolean dispatchDragEvent(android.view.DragEvent);
-    method protected void dispatchDraw(android.graphics.Canvas);
+    method protected void dispatchDraw(@NonNull android.graphics.Canvas);
     method public void dispatchDrawableHotspotChanged(float, float);
     method @CallSuper public void dispatchFinishTemporaryDetach();
     method protected boolean dispatchGenericFocusedEvent(android.view.MotionEvent);
@@ -50083,7 +50150,7 @@
     method @NonNull public android.view.WindowInsetsAnimation.Bounds dispatchWindowInsetsAnimationStart(@NonNull android.view.WindowInsetsAnimation, @NonNull android.view.WindowInsetsAnimation.Bounds);
     method @Deprecated public void dispatchWindowSystemUiVisiblityChanged(int);
     method public void dispatchWindowVisibilityChanged(int);
-    method @CallSuper public void draw(android.graphics.Canvas);
+    method @CallSuper public void draw(@NonNull android.graphics.Canvas);
     method @CallSuper public void drawableHotspotChanged(float, float);
     method @CallSuper protected void drawableStateChanged();
     method public android.view.View findFocus();
@@ -50160,6 +50227,7 @@
     method public float getHandwritingBoundsOffsetLeft();
     method public float getHandwritingBoundsOffsetRight();
     method public float getHandwritingBoundsOffsetTop();
+    method @Nullable public android.view.HandwritingDelegateConfiguration getHandwritingDelegateConfiguration();
     method public final boolean getHasOverlappingRendering();
     method public final int getHeight();
     method public void getHitRect(android.graphics.Rect);
@@ -50375,9 +50443,9 @@
     method @CallSuper protected void onDetachedFromWindow();
     method protected void onDisplayHint(int);
     method public boolean onDragEvent(android.view.DragEvent);
-    method protected void onDraw(android.graphics.Canvas);
-    method public void onDrawForeground(android.graphics.Canvas);
-    method protected final void onDrawScrollBars(android.graphics.Canvas);
+    method protected void onDraw(@NonNull android.graphics.Canvas);
+    method public void onDrawForeground(@NonNull android.graphics.Canvas);
+    method protected final void onDrawScrollBars(@NonNull android.graphics.Canvas);
     method public boolean onFilterTouchEventForSecurity(android.view.MotionEvent);
     method @CallSuper protected void onFinishInflate();
     method public void onFinishTemporaryDetach();
@@ -50526,6 +50594,7 @@
     method public void setForegroundTintList(@Nullable android.content.res.ColorStateList);
     method public void setForegroundTintMode(@Nullable android.graphics.PorterDuff.Mode);
     method public void setHandwritingBoundsOffsets(float, float, float, float);
+    method public void setHandwritingDelegateConfiguration(@Nullable android.view.HandwritingDelegateConfiguration);
     method public void setHapticFeedbackEnabled(boolean);
     method public void setHasTransientState(boolean);
     method public void setHorizontalFadingEdgeEnabled(boolean);
@@ -50862,7 +50931,7 @@
     ctor public View.DragShadowBuilder(android.view.View);
     ctor public View.DragShadowBuilder();
     method public final android.view.View getView();
-    method public void onDrawShadow(android.graphics.Canvas);
+    method public void onDrawShadow(@NonNull android.graphics.Canvas);
     method public void onProvideShadowMetrics(android.graphics.Point, android.graphics.Point);
   }
 
@@ -51092,7 +51161,7 @@
     method public void dispatchSetActivated(boolean);
     method public void dispatchSetSelected(boolean);
     method protected void dispatchThawSelfOnly(android.util.SparseArray<android.os.Parcelable>);
-    method protected boolean drawChild(android.graphics.Canvas, android.view.View, long);
+    method protected boolean drawChild(@NonNull android.graphics.Canvas, android.view.View, long);
     method public void endViewTransition(android.view.View);
     method public android.view.View focusSearch(android.view.View, int);
     method public void focusableViewAvailable(android.view.View);
@@ -53305,6 +53374,7 @@
     method public android.graphics.Matrix getMatrix();
     method public int getSelectionEnd();
     method public int getSelectionStart();
+    method @Nullable public android.view.inputmethod.TextAppearanceInfo getTextAppearanceInfo();
     method @NonNull public java.util.List<android.graphics.RectF> getVisibleLineBounds();
     method public void writeToParcel(android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.CursorAnchorInfo> CREATOR;
@@ -53325,6 +53395,7 @@
     method public android.view.inputmethod.CursorAnchorInfo.Builder setInsertionMarkerLocation(float, float, float, float, int);
     method public android.view.inputmethod.CursorAnchorInfo.Builder setMatrix(android.graphics.Matrix);
     method public android.view.inputmethod.CursorAnchorInfo.Builder setSelectionRange(int, int);
+    method @NonNull public android.view.inputmethod.CursorAnchorInfo.Builder setTextAppearanceInfo(@Nullable android.view.inputmethod.TextAppearanceInfo);
   }
 
   public final class DeleteGesture extends android.view.inputmethod.HandwritingGesture implements android.os.Parcelable {
@@ -53568,6 +53639,7 @@
     method public boolean reportFullscreenMode(boolean);
     method public boolean requestCursorUpdates(int);
     method public default boolean requestCursorUpdates(int, int);
+    method public default void requestTextBoundsInfo(@NonNull android.graphics.RectF, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<android.view.inputmethod.TextBoundsInfoResult>);
     method public boolean sendKeyEvent(android.view.KeyEvent);
     method public boolean setComposingRegion(int, int);
     method public default boolean setComposingRegion(int, int, @Nullable android.view.inputmethod.TextAttribute);
@@ -53579,6 +53651,7 @@
     field public static final int CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS = 8; // 0x8
     field public static final int CURSOR_UPDATE_FILTER_EDITOR_BOUNDS = 4; // 0x4
     field public static final int CURSOR_UPDATE_FILTER_INSERTION_MARKER = 16; // 0x10
+    field public static final int CURSOR_UPDATE_FILTER_TEXT_APPEARANCE = 64; // 0x40
     field public static final int CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS = 32; // 0x20
     field public static final int CURSOR_UPDATE_IMMEDIATE = 1; // 0x1
     field public static final int CURSOR_UPDATE_MONITOR = 2; // 0x2
@@ -53882,6 +53955,35 @@
     field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.SurroundingText> CREATOR;
   }
 
+  public final class TextAppearanceInfo implements android.os.Parcelable {
+    ctor public TextAppearanceInfo(@NonNull android.widget.TextView);
+    method public int describeContents();
+    method @Nullable public String getFontFamilyName();
+    method @Nullable public String getFontFeatureSettings();
+    method @Nullable public String getFontVariationSettings();
+    method public float getLetterSpacing();
+    method public int getLineBreakStyle();
+    method public int getLineBreakWordStyle();
+    method public int getMaxLength();
+    method @Px public float getShadowDx();
+    method @Px public float getShadowDy();
+    method @Px public float getShadowRadius();
+    method @ColorInt public int getTextColor();
+    method @ColorInt public int getTextColorHighlight();
+    method @ColorInt public int getTextColorHint();
+    method @Nullable public android.content.res.ColorStateList getTextColorLink();
+    method @IntRange(from=0xffffffff, to=android.graphics.fonts.FontStyle.FONT_WEIGHT_MAX) public int getTextFontWeight();
+    method @NonNull public android.os.LocaleList getTextLocales();
+    method public float getTextScaleX();
+    method @Px public float getTextSize();
+    method public int getTextStyle();
+    method public boolean isAllCaps();
+    method public boolean isElegantTextHeight();
+    method public boolean isFallbackLineSpacing();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.TextAppearanceInfo> CREATOR;
+  }
+
   public final class TextAttribute implements android.os.Parcelable {
     method public int describeContents();
     method @NonNull public android.os.PersistableBundle getExtras();
@@ -53897,6 +53999,50 @@
     method @NonNull public android.view.inputmethod.TextAttribute.Builder setTextConversionSuggestions(@NonNull java.util.List<java.lang.String>);
   }
 
+  public final class TextBoundsInfo implements android.os.Parcelable {
+    method public int describeContents();
+    method @IntRange(from=0, to=125) public int getCharacterBidiLevel(int);
+    method @NonNull public android.graphics.RectF getCharacterBounds(int);
+    method public int getCharacterFlags(int);
+    method public int getEnd();
+    method @NonNull public android.text.SegmentFinder getGraphemeSegmentFinder();
+    method @NonNull public android.text.SegmentFinder getLineSegmentFinder();
+    method @NonNull public android.graphics.Matrix getMatrix();
+    method public int getStart();
+    method @NonNull public android.text.SegmentFinder getWordSegmentFinder();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.view.inputmethod.TextBoundsInfo> CREATOR;
+    field public static final int FLAG_CHARACTER_LINEFEED = 2; // 0x2
+    field public static final int FLAG_CHARACTER_PUNCTUATION = 4; // 0x4
+    field public static final int FLAG_CHARACTER_WHITESPACE = 1; // 0x1
+    field public static final int FLAG_LINE_IS_RTL = 8; // 0x8
+  }
+
+  public static final class TextBoundsInfo.Builder {
+    ctor public TextBoundsInfo.Builder();
+    method @NonNull public android.view.inputmethod.TextBoundsInfo build();
+    method @NonNull public android.view.inputmethod.TextBoundsInfo.Builder clear();
+    method @NonNull public android.view.inputmethod.TextBoundsInfo.Builder setCharacterBidiLevel(@NonNull int[]);
+    method @NonNull public android.view.inputmethod.TextBoundsInfo.Builder setCharacterBounds(@NonNull float[]);
+    method @NonNull public android.view.inputmethod.TextBoundsInfo.Builder setCharacterFlags(@NonNull int[]);
+    method @NonNull public android.view.inputmethod.TextBoundsInfo.Builder setGraphemeSegmentFinder(@NonNull android.text.SegmentFinder);
+    method @NonNull public android.view.inputmethod.TextBoundsInfo.Builder setLineSegmentFinder(@NonNull android.text.SegmentFinder);
+    method @NonNull public android.view.inputmethod.TextBoundsInfo.Builder setMatrix(@NonNull android.graphics.Matrix);
+    method @NonNull public android.view.inputmethod.TextBoundsInfo.Builder setStartAndEnd(@IntRange(from=0) int, @IntRange(from=0) int);
+    method @NonNull public android.view.inputmethod.TextBoundsInfo.Builder setWordSegmentFinder(@NonNull android.text.SegmentFinder);
+  }
+
+  public final class TextBoundsInfoResult {
+    ctor public TextBoundsInfoResult(int);
+    ctor public TextBoundsInfoResult(int, @NonNull android.view.inputmethod.TextBoundsInfo);
+    method public int getResultCode();
+    method @Nullable public android.view.inputmethod.TextBoundsInfo getTextBoundsInfo();
+    field public static final int CODE_CANCELLED = 3; // 0x3
+    field public static final int CODE_FAILED = 2; // 0x2
+    field public static final int CODE_SUCCESS = 1; // 0x1
+    field public static final int CODE_UNSUPPORTED = 0; // 0x0
+  }
+
   public final class TextSnapshot {
     ctor public TextSnapshot(@NonNull android.view.inputmethod.SurroundingText, @IntRange(from=0xffffffff) int, @IntRange(from=0xffffffff) int, int);
     method @IntRange(from=0xffffffff) public int getCompositionEnd();
diff --git a/core/api/module-lib-current.txt b/core/api/module-lib-current.txt
index 88efcce..ce18745 100644
--- a/core/api/module-lib-current.txt
+++ b/core/api/module-lib-current.txt
@@ -340,10 +340,6 @@
     method public boolean shouldBypassCache(@NonNull Q);
   }
 
-  public interface Parcelable {
-    method public default int getStability();
-  }
-
   public class Process {
     method public static final int getAppUidForSdkSandboxUid(int);
     method public static final boolean isSdkSandboxUid(int);
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index a382ecf..70b89b8 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -524,10 +524,12 @@
   }
 
   public class AlarmManager {
-    method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void set(int, long, long, long, android.app.PendingIntent, android.os.WorkSource);
-    method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void set(int, long, long, long, android.app.AlarmManager.OnAlarmListener, android.os.Handler, android.os.WorkSource);
+    method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void set(int, long, long, long, @NonNull android.app.PendingIntent, @Nullable android.os.WorkSource);
+    method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void set(int, long, long, long, @NonNull android.app.AlarmManager.OnAlarmListener, @Nullable android.os.Handler, @Nullable android.os.WorkSource);
     method @RequiresPermission(allOf={android.Manifest.permission.UPDATE_DEVICE_STATS, android.Manifest.permission.SCHEDULE_EXACT_ALARM}, conditional=true) public void setExact(int, long, @Nullable String, @NonNull java.util.concurrent.Executor, @NonNull android.os.WorkSource, @NonNull android.app.AlarmManager.OnAlarmListener);
+    method @RequiresPermission(allOf={android.Manifest.permission.UPDATE_DEVICE_STATS, android.Manifest.permission.SCHEDULE_EXACT_ALARM}, conditional=true) public void setExactAndAllowWhileIdle(int, long, @Nullable String, @NonNull java.util.concurrent.Executor, @Nullable android.os.WorkSource, @NonNull android.app.AlarmManager.OnAlarmListener);
     method @RequiresPermission(android.Manifest.permission.SCHEDULE_PRIORITIZED_ALARM) public void setPrioritized(int, long, long, @Nullable String, @NonNull java.util.concurrent.Executor, @NonNull android.app.AlarmManager.OnAlarmListener);
+    method @RequiresPermission(android.Manifest.permission.UPDATE_DEVICE_STATS) public void setWindow(int, long, long, @Nullable String, @NonNull java.util.concurrent.Executor, @Nullable android.os.WorkSource, @NonNull android.app.AlarmManager.OnAlarmListener);
   }
 
   public class AppOpsManager {
@@ -585,6 +587,7 @@
     field public static final String OPSTR_READ_MEDIA_AUDIO = "android:read_media_audio";
     field public static final String OPSTR_READ_MEDIA_IMAGES = "android:read_media_images";
     field public static final String OPSTR_READ_MEDIA_VIDEO = "android:read_media_video";
+    field public static final String OPSTR_READ_MEDIA_VISUAL_USER_SELECTED = "android:read_media_visual_user_selected";
     field public static final String OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO = "android:receive_ambient_trigger_audio";
     field public static final String OPSTR_RECEIVE_EMERGENCY_BROADCAST = "android:receive_emergency_broadcast";
     field public static final String OPSTR_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO = "android:receive_explicit_user_interaction_audio";
@@ -6343,12 +6346,21 @@
   public final class AudioPlaybackConfiguration implements android.os.Parcelable {
     method public int getClientPid();
     method public int getClientUid();
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public int getMutedBy();
     method public int getPlayerInterfaceId();
     method public android.media.PlayerProxy getPlayerProxy();
     method public int getPlayerState();
     method public int getPlayerType();
     method @IntRange(from=0) public int getSessionId();
     method public boolean isActive();
+    method @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public boolean isMuted();
+    field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_APP_OPS = 8; // 0x8
+    field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_CLIENT_VOLUME = 16; // 0x10
+    field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_MASTER = 1; // 0x1
+    field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_STREAM_MUTED = 4; // 0x4
+    field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_STREAM_VOLUME = 2; // 0x2
+    field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_UNKNOWN = -1; // 0xffffffff
+    field @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING) public static final int MUTED_BY_VOLUME_SHAPER = 32; // 0x20
     field public static final int PLAYER_STATE_IDLE = 1; // 0x1
     field public static final int PLAYER_STATE_PAUSED = 3; // 0x3
     field public static final int PLAYER_STATE_RELEASED = 0; // 0x0
@@ -9670,6 +9682,7 @@
   }
 
   public interface Parcelable {
+    method public default int getStability();
     field public static final int PARCELABLE_STABILITY_LOCAL = 0; // 0x0
     field public static final int PARCELABLE_STABILITY_VINTF = 1; // 0x1
   }
@@ -9678,7 +9691,6 @@
     ctor public ParcelableHolder(int);
     method public int describeContents();
     method @Nullable public <T extends android.os.Parcelable> T getParcelable(@NonNull Class<T>);
-    method public int getStability();
     method public void readFromParcel(@NonNull android.os.Parcel);
     method public void setParcelable(@Nullable android.os.Parcelable);
     method public void writeToParcel(@NonNull android.os.Parcel, int);
@@ -13704,6 +13716,7 @@
     field public static final String ACTION_SIM_SLOT_STATUS_CHANGED = "android.telephony.action.SIM_SLOT_STATUS_CHANGED";
     field public static final int ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G = 3; // 0x3
     field public static final int ALLOWED_NETWORK_TYPES_REASON_POWER = 1; // 0x1
+    field public static final int ALLOWED_NETWORK_TYPES_REASON_USER_RESTRICTIONS = 4; // 0x4
     field public static final int CALL_WAITING_STATUS_DISABLED = 2; // 0x2
     field public static final int CALL_WAITING_STATUS_ENABLED = 1; // 0x1
     field public static final int CALL_WAITING_STATUS_FDN_CHECK_FAILURE = 5; // 0x5
@@ -15921,10 +15934,17 @@
 
 package android.view.accessibility {
 
+  public abstract class AccessibilityDisplayProxy {
+    ctor public AccessibilityDisplayProxy(int, @NonNull java.util.concurrent.Executor, @NonNull java.util.List<android.accessibilityservice.AccessibilityServiceInfo>);
+    method public int getDisplayId();
+  }
+
   public final class AccessibilityManager {
     method public int getAccessibilityWindowId(@Nullable android.os.IBinder);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public void performAccessibilityShortcut();
+    method @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public boolean registerDisplayProxy(@NonNull android.view.accessibility.AccessibilityDisplayProxy);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public void registerSystemAction(@NonNull android.app.RemoteAction, int);
+    method @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public boolean unregisterDisplayProxy(@NonNull android.view.accessibility.AccessibilityDisplayProxy);
     method @RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY) public void unregisterSystemAction(int);
   }
 
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index ef74a3e..e9f9136 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -47,6 +47,7 @@
     field public static final String SET_KEYBOARD_LAYOUT = "android.permission.SET_KEYBOARD_LAYOUT";
     field public static final String SUSPEND_APPS = "android.permission.SUSPEND_APPS";
     field public static final String TEST_BIOMETRIC = "android.permission.TEST_BIOMETRIC";
+    field public static final String TEST_INPUT_METHOD = "android.permission.TEST_INPUT_METHOD";
     field public static final String TEST_MANAGE_ROLLBACKS = "android.permission.TEST_MANAGE_ROLLBACKS";
     field public static final String UPGRADE_RUNTIME_PERMISSIONS = "android.permission.UPGRADE_RUNTIME_PERMISSIONS";
     field public static final String WRITE_DEVICE_CONFIG = "android.permission.WRITE_DEVICE_CONFIG";
@@ -419,7 +420,7 @@
   }
 
   public final class SyncNotedAppOp implements android.os.Parcelable {
-    ctor public SyncNotedAppOp(int, @IntRange(from=0L) int, @Nullable String, @NonNull String);
+    ctor public SyncNotedAppOp(int, @IntRange(from=0L) int, @Nullable String, @Nullable String);
   }
 
   public class TaskInfo {
@@ -3156,12 +3157,12 @@
   }
 
   public final class InputMethodManager {
-    method public void addVirtualStylusIdForTestSession();
+    method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public void addVirtualStylusIdForTestSession();
     method public int getDisplayId();
-    method @NonNull @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public java.util.List<android.view.inputmethod.InputMethodInfo> getInputMethodListAsUser(int);
+    method @NonNull @RequiresPermission(value=android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional=true) public java.util.List<android.view.inputmethod.InputMethodInfo> getInputMethodListAsUser(int);
     method public boolean hasActiveInputConnection(@Nullable android.view.View);
-    method public boolean isInputMethodPickerShown();
-    method @RequiresPermission("android.permission.TEST_INPUT_METHOD") public void setStylusWindowIdleTimeoutForTest(long);
+    method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public boolean isInputMethodPickerShown();
+    method @RequiresPermission(android.Manifest.permission.TEST_INPUT_METHOD) public void setStylusWindowIdleTimeoutForTest(long);
     field public static final long CLEAR_SHOW_FORCED_FLAG_WHEN_LEAVING = 214016041L; // 0xcc1a029L
   }
 
diff --git a/core/java/android/accounts/AbstractAccountAuthenticator.java b/core/java/android/accounts/AbstractAccountAuthenticator.java
index 89601bc..45515dd 100644
--- a/core/java/android/accounts/AbstractAccountAuthenticator.java
+++ b/core/java/android/accounts/AbstractAccountAuthenticator.java
@@ -117,27 +117,27 @@
     /**
      * Bundle key used for the {@link String} account type in session bundle.
      * This is used in the default implementation of
-     * {@link #startAddAccountSession} and {@link startUpdateCredentialsSession}.
+     * {@link #startAddAccountSession} and {@link #startUpdateCredentialsSession}.
      */
     private static final String KEY_AUTH_TOKEN_TYPE =
             "android.accounts.AbstractAccountAuthenticato.KEY_AUTH_TOKEN_TYPE";
     /**
      * Bundle key used for the {@link String} array of required features in
      * session bundle. This is used in the default implementation of
-     * {@link #startAddAccountSession} and {@link startUpdateCredentialsSession}.
+     * {@link #startAddAccountSession} and {@link #startUpdateCredentialsSession}.
      */
     private static final String KEY_REQUIRED_FEATURES =
             "android.accounts.AbstractAccountAuthenticator.KEY_REQUIRED_FEATURES";
     /**
      * Bundle key used for the {@link Bundle} options in session bundle. This is
      * used in default implementation of {@link #startAddAccountSession} and
-     * {@link startUpdateCredentialsSession}.
+     * {@link #startUpdateCredentialsSession}.
      */
     private static final String KEY_OPTIONS =
             "android.accounts.AbstractAccountAuthenticator.KEY_OPTIONS";
     /**
      * Bundle key used for the {@link Account} account in session bundle. This is used
-     * used in default implementation of {@link startUpdateCredentialsSession}.
+     * used in default implementation of {@link #startUpdateCredentialsSession}.
      */
     private static final String KEY_ACCOUNT =
             "android.accounts.AbstractAccountAuthenticator.KEY_ACCOUNT";
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index a573776..b3db38d 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -27,7 +27,6 @@
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
 import android.annotation.UserHandleAware;
-import android.annotation.UserIdInt;
 import android.app.Activity;
 import android.app.PropertyInvalidatedCache;
 import android.compat.annotation.UnsupportedAppUsage;
@@ -37,6 +36,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.IntentSender;
+import android.content.pm.UserPackage;
 import android.content.res.Resources;
 import android.database.SQLException;
 import android.os.Build;
@@ -348,43 +348,11 @@
     */
     public static final int CACHE_ACCOUNTS_DATA_SIZE = 4;
 
-    private static final class UserIdPackage
-    {
-        @UserIdInt
-        public int userId;
-        public String packageName;
-
-        public UserIdPackage(int UserId, String PackageName) {
-            this.userId = UserId;
-            this.packageName = PackageName;
-        }
-
-        @Override
-        public boolean equals(@Nullable Object o) {
-            if (o == null) {
-                return false;
-            }
-            if (o == this) {
-                return true;
-            }
-            if (o.getClass() != getClass()) {
-                return false;
-            }
-            UserIdPackage e = (UserIdPackage) o;
-            return e.userId == userId && e.packageName.equals(packageName);
-        }
-
-        @Override
-        public int hashCode() {
-            return userId ^ packageName.hashCode();
-        }
-    }
-
-    PropertyInvalidatedCache<UserIdPackage, Account[]> mAccountsForUserCache =
-                new PropertyInvalidatedCache<UserIdPackage, Account[]>(
+    PropertyInvalidatedCache<UserPackage, Account[]> mAccountsForUserCache =
+                new PropertyInvalidatedCache<UserPackage, Account[]>(
                 CACHE_ACCOUNTS_DATA_SIZE, CACHE_KEY_ACCOUNTS_DATA_PROPERTY) {
         @Override
-        public Account[] recompute(UserIdPackage userAndPackage) {
+        public Account[] recompute(UserPackage userAndPackage) {
             try {
                 return mService.getAccountsAsUser(null, userAndPackage.userId, userAndPackage.packageName);
             } catch (RemoteException e) {
@@ -392,7 +360,7 @@
             }
         }
         @Override
-        public boolean bypass(UserIdPackage query) {
+        public boolean bypass(UserPackage query) {
             return query.userId < 0;
         }
         @Override
@@ -731,7 +699,7 @@
      */
     @NonNull
     public Account[] getAccountsAsUser(int userId) {
-        UserIdPackage userAndPackage = new UserIdPackage(userId, mContext.getOpPackageName());
+        UserPackage userAndPackage = UserPackage.of(userId, mContext.getOpPackageName());
         return mAccountsForUserCache.query(userAndPackage);
     }
 
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 32d0d75..501b136 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -8364,11 +8364,17 @@
     }
 
     final void performNewIntent(@NonNull Intent intent) {
+        Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "performNewIntent");
         mCanEnterPictureInPicture = true;
         onNewIntent(intent);
+        Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
     }
 
     final void performStart(String reason) {
+        if (Trace.isTagEnabled(Trace.TRACE_TAG_WINDOW_MANAGER)) {
+            Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "performStart:"
+                    + mComponent.getClassName());
+        }
         dispatchActivityPreStarted();
         mActivityTransitionState.setEnterActivityOptions(this, getActivityOptions());
         mFragments.noteStateNotSaved();
@@ -8415,6 +8421,7 @@
 
         mActivityTransitionState.enterReady(this);
         dispatchActivityPostStarted();
+        Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
     }
 
     /**
@@ -8423,7 +8430,8 @@
      *              The option to not start immediately is needed in case a transaction with
      *              multiple lifecycle transitions is in progress.
      */
-    final void performRestart(boolean start, String reason) {
+    final void performRestart(boolean start) {
+        Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "performRestart");
         mCanEnterPictureInPicture = true;
         mFragments.noteStateNotSaved();
 
@@ -8458,17 +8466,18 @@
             final long startTime = SystemClock.uptimeMillis();
             mInstrumentation.callActivityOnRestart(this);
             final long duration = SystemClock.uptimeMillis() - startTime;
-            EventLogTags.writeWmOnRestartCalled(mIdent, getComponentName().getClassName(), reason,
-                    duration);
+            EventLogTags.writeWmOnRestartCalled(mIdent, getComponentName().getClassName(),
+                    "performRestart", duration);
             if (!mCalled) {
                 throw new SuperNotCalledException(
                     "Activity " + mComponent.toShortString() +
                     " did not call through to super.onRestart()");
             }
             if (start) {
-                performStart(reason);
+                performStart("performRestart");
             }
         }
+        Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
     }
 
     final void performResume(boolean followedByPause, String reason) {
@@ -8477,7 +8486,6 @@
                     + mComponent.getClassName());
         }
         dispatchActivityPreResumed();
-        performRestart(true /* start */, reason);
 
         mFragments.execPendingActions();
 
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 4b1b0a2..baa3bd9 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -227,6 +227,12 @@
     public abstract boolean isModernQueueEnabled();
 
     /**
+     * Enforce capability restrictions on use of the given BroadcastOptions
+     */
+    public abstract void enforceBroadcastOptionsPermissions(@Nullable Bundle options,
+            int callingUid);
+
+    /**
      * Returns package name given pid.
      *
      * @param pid The pid we are searching package name for.
@@ -300,7 +306,7 @@
     public abstract int handleIncomingUser(int callingPid, int callingUid, @UserIdInt int userId,
             boolean allowAll, int allowMode, String name, String callerPackage);
 
-    /** Checks if the calling binder pid as the permission. */
+    /** Checks if the calling binder pid/uid has the given permission. */
     @PermissionMethod
     public abstract void enforceCallingPermission(@PermissionName String permission, String func);
 
@@ -389,6 +395,10 @@
      */
     public abstract boolean isAppStartModeDisabled(int uid, String packageName);
 
+    /**
+     * Returns the ids of the current user and all of its profiles (if any), regardless of the
+     * running state of the profiles.
+     */
     public abstract int[] getCurrentProfileIds();
     public abstract UserInfo getCurrentUser();
     public abstract void ensureNotSpecialUser(@UserIdInt int userId);
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 67d4416..1f63343 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -173,7 +173,6 @@
 import android.util.proto.ProtoOutputStream;
 import android.view.Choreographer;
 import android.view.Display;
-import android.view.DisplayAdjustments;
 import android.view.SurfaceControl;
 import android.view.ThreadedRenderer;
 import android.view.View;
@@ -244,7 +243,6 @@
 import java.util.TimeZone;
 import java.util.concurrent.Executor;
 import java.util.concurrent.atomic.AtomicInteger;
-import java.util.function.Consumer;
 
 /**
  * This manages the execution of the main thread in an
@@ -409,7 +407,6 @@
     boolean mInstrumentingWithoutRestart;
     boolean mSystemThread = false;
     boolean mSomeActivitiesChanged = false;
-    /* package */ boolean mHiddenApiWarningShown = false;
 
     // These can be accessed by multiple threads; mResourcesManager is the lock.
     // XXX For now we keep around information about all packages we have
@@ -438,16 +435,10 @@
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     private final ResourcesManager mResourcesManager;
 
-    /** The active adjustments that override the {@link DisplayAdjustments} in resources. */
-    private ArrayList<Pair<IBinder, Consumer<DisplayAdjustments>>> mActiveRotationAdjustments;
-
     // Registry of remote cancellation transports pending a reply with reply handles.
     @GuardedBy("this")
     private @Nullable Map<SafeCancellationTransport, CancellationSignal> mRemoteCancellations;
 
-    private final Map<IBinder, Integer> mLastReportedWindowingMode = Collections.synchronizedMap(
-            new ArrayMap<>());
-
     private static final class ProviderKey {
         final String authority;
         final int userId;
@@ -518,8 +509,6 @@
      */
     private final Object mCoreSettingsLock = new Object();
 
-    boolean mHasImeComponent = false;
-
     private IContentCaptureOptionsCallback.Stub mContentCaptureOptionsCallback = null;
 
     /** A client side controller to handle process level configuration changes. */
@@ -599,6 +588,12 @@
         /** Whether this activiy was launched from a bubble. */
         boolean mLaunchedFromBubble;
 
+        /**
+         * This can be different from the current configuration because a new configuration may not
+         * always update to activity, e.g. windowing mode change without size change.
+         */
+        int mLastReportedWindowingMode = WINDOWING_MODE_UNDEFINED;
+
         @LifecycleState
         private int mLifecycleState = PRE_ON_CREATE;
 
@@ -3654,8 +3649,7 @@
                         "Activity " + r.intent.getComponent().toShortString() +
                         " did not call through to super.onCreate()");
                 }
-                mLastReportedWindowingMode.put(activity.getActivityToken(),
-                        config.windowConfiguration.getWindowingMode());
+                r.mLastReportedWindowingMode = config.windowConfiguration.getWindowingMode();
             }
             r.setState(ON_CREATE);
 
@@ -3714,12 +3708,14 @@
         // Call postOnCreate()
         if (pendingActions.shouldCallOnPostCreate()) {
             activity.mCalled = false;
+            Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "onPostCreate");
             if (r.isPersistable()) {
                 mInstrumentation.callActivityOnPostCreate(activity, r.state,
                         r.persistentState);
             } else {
                 mInstrumentation.callActivityOnPostCreate(activity, r.state);
             }
+            Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
             if (!activity.mCalled) {
                 throw new SuperNotCalledException(
                         "Activity " + r.intent.getComponent().toShortString()
@@ -5268,7 +5264,7 @@
     @Override
     public void performRestartActivity(ActivityClientRecord r, boolean start) {
         if (r.stopped) {
-            r.activity.performRestart(start, "performRestartActivity");
+            r.activity.performRestart(start);
             if (start) {
                 r.setState(ON_START);
             }
@@ -5285,7 +5281,7 @@
     private void onCoreSettingsChange() {
         if (updateDebugViewAttributeState()) {
             // request all activities to relaunch for the changes to take place
-            relaunchAllActivities(false /* preserveWindows */, "onCoreSettingsChange");
+            relaunchAllActivities(true /* preserveWindows */, "onCoreSettingsChange");
         }
     }
 
@@ -5433,7 +5429,6 @@
             }
         }
         r.setState(ON_DESTROY);
-        mLastReportedWindowingMode.remove(r.activity.getActivityToken());
         schedulePurgeIdler();
         synchronized (this) {
             if (mSplashScreenGlobal != null) {
@@ -5865,7 +5860,7 @@
         if (r.overrideConfig != null) {
             r.tmpConfig.updateFrom(r.overrideConfig);
         }
-        final Configuration reportedConfig = performActivityConfigurationChanged(r.activity,
+        final Configuration reportedConfig = performActivityConfigurationChanged(r,
                 r.tmpConfig, r.overrideConfig, displayId, alwaysReportChange);
         freeTextLayoutCachesIfNeeded(r.activity.mCurrentConfig.diff(r.tmpConfig));
         return reportedConfig;
@@ -5873,7 +5868,7 @@
 
     /**
      * Decides whether to update an Activity's configuration and whether to inform it.
-     * @param activity The activity to notify of configuration change.
+     * @param r The activity client record to notify of configuration change.
      * @param newConfig The new configuration.
      * @param amOverrideConfig The override config that differentiates the Activity's configuration
      *                         from the base global configuration. This is supplied by
@@ -5881,29 +5876,29 @@
      * @param displayId Id of the display where activity currently resides.
      * @return Configuration sent to client, null if no changes and not moved to different display.
      */
-    private Configuration performActivityConfigurationChanged(Activity activity,
+    private Configuration performActivityConfigurationChanged(ActivityClientRecord r,
             Configuration newConfig, Configuration amOverrideConfig, int displayId,
             boolean alwaysReportChange) {
+        final Activity activity = r.activity;
         final IBinder activityToken = activity.getActivityToken();
 
         // WindowConfiguration differences aren't considered as public, check it separately.
         // multi-window / pip mode changes, if any, should be sent before the configuration
         // change callback, see also PinnedStackTests#testConfigurationChangeOrderDuringTransition
-        handleWindowingModeChangeIfNeeded(activity, newConfig);
+        handleWindowingModeChangeIfNeeded(r, newConfig);
 
         final boolean movedToDifferentDisplay = isDifferentDisplay(activity.getDisplayId(),
                 displayId);
         final Configuration currentResConfig = activity.getResources().getConfiguration();
         final int diff = currentResConfig.diffPublicOnly(newConfig);
         final boolean hasPublicResConfigChange = diff != 0;
-        final ActivityClientRecord r = getActivityClient(activityToken);
         // TODO(b/173090263): Use diff instead after the improvement of AssetManager and
         // ResourcesImpl constructions.
         final boolean shouldUpdateResources = hasPublicResConfigChange
                 || shouldUpdateResources(activityToken, currentResConfig, newConfig,
                 amOverrideConfig, movedToDifferentDisplay, hasPublicResConfigChange);
         final boolean shouldReportChange = shouldReportChange(
-                activity.mCurrentConfig, newConfig, r != null ? r.mSizeConfigurations : null,
+                activity.mCurrentConfig, newConfig, r.mSizeConfigurations,
                 activity.mActivityInfo.getRealConfigChanged(), alwaysReportChange);
         // Nothing significant, don't proceed with updating and reporting.
         if (!shouldUpdateResources && !shouldReportChange) {
@@ -6012,12 +6007,11 @@
      * See also {@link Activity#onMultiWindowModeChanged(boolean, Configuration)} and
      * {@link Activity#onPictureInPictureModeChanged(boolean, Configuration)}
      */
-    private void handleWindowingModeChangeIfNeeded(Activity activity,
+    private void handleWindowingModeChangeIfNeeded(ActivityClientRecord r,
             Configuration newConfiguration) {
+        final Activity activity = r.activity;
         final int newWindowingMode = newConfiguration.windowConfiguration.getWindowingMode();
-        final IBinder token = activity.getActivityToken();
-        final int oldWindowingMode = mLastReportedWindowingMode.getOrDefault(token,
-                WINDOWING_MODE_UNDEFINED);
+        final int oldWindowingMode = r.mLastReportedWindowingMode;
         if (oldWindowingMode == newWindowingMode) return;
         // PiP callback is sent before the MW one.
         if (newWindowingMode == WINDOWING_MODE_PINNED) {
@@ -6032,7 +6026,7 @@
         if (wasInMultiWindowMode != nowInMultiWindowMode) {
             activity.dispatchMultiWindowModeChanged(nowInMultiWindowMode, newConfiguration);
         }
-        mLastReportedWindowingMode.put(token, newWindowingMode);
+        r.mLastReportedWindowingMode = newWindowingMode;
     }
 
     /**
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index d1275f6..dc325ff 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -1353,9 +1353,46 @@
     public static final int OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO =
             AppProtoEnums.APP_OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO;
 
+    /**
+     * App can schedule long running jobs.
+     *
+     * @hide
+     */
+    public static final int OP_RUN_LONG_JOBS = AppProtoEnums.APP_OP_RUN_LONG_JOBS;
+
+    /**
+     * Notify apps that they have been granted URI permission photos
+     *
+     * @hide
+     */
+    public static final int OP_READ_MEDIA_VISUAL_USER_SELECTED =
+            AppProtoEnums.APP_OP_READ_MEDIA_VISUAL_USER_SELECTED;
+
+    /**
+     * Prevent an app from being placed into app standby buckets.
+     *
+     * Only to be used by the system.
+     *
+     * @hide
+     */
+    public static final int OP_SYSTEM_EXEMPT_FROM_APP_STANDBY =
+            AppProtoEnums.APP_OP_SYSTEM_EXEMPT_FROM_APP_STANDBY;
+
+    /**
+     * Prevent an app from being placed into forced app standby.
+     * {@link ActivityManager#isBackgroundRestricted()}
+     * {@link #OP_RUN_ANY_IN_BACKGROUND}
+     *
+     * Only to be used by the system.
+     *
+     * @hide
+     */
+    public static final int OP_SYSTEM_EXEMPT_FROM_FORCED_APP_STANDBY =
+            AppProtoEnums.APP_OP_SYSTEM_EXEMPT_FROM_FORCED_APP_STANDBY;
+
     /** @hide */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    public static final int _NUM_OP = 122;
+    public static final int _NUM_OP = 126;
 
     /** Access to coarse location information. */
     public static final String OPSTR_COARSE_LOCATION = "android:coarse_location";
@@ -1826,6 +1863,14 @@
     @SystemApi
     public static final String OPSTR_RECEIVE_AMBIENT_TRIGGER_AUDIO =
             "android:receive_ambient_trigger_audio";
+    /**
+     * Notify apps that they have been granted URI permission photos
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final String OPSTR_READ_MEDIA_VISUAL_USER_SELECTED =
+            "android:read_media_visual_user_selected";
 
     /**
      * Record audio from near-field microphone (ie. TV remote)
@@ -1839,6 +1884,35 @@
     public static final String OPSTR_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO =
             "android:receive_explicit_user_interaction_audio";
 
+    /**
+     * App can schedule long running jobs.
+     *
+     * @hide
+     */
+    public static final String OPSTR_RUN_LONG_JOBS = "android:run_long_jobs";
+
+    /**
+     * Prevent an app from being placed into app standby buckets.
+     *
+     * Only to be used by the system.
+     *
+     * @hide
+     */
+    public static final String OPSTR_SYSTEM_EXEMPT_FROM_APP_STANDBY =
+            "android:system_exempt_from_app_standby";
+
+    /**
+     * Prevent an app from being placed into forced app standby.
+     * {@link ActivityManager#isBackgroundRestricted()}
+     * {@link #OP_RUN_ANY_IN_BACKGROUND}
+     *
+     * Only to be used by the system.
+     *
+     * @hide
+     */
+    public static final String OPSTR_SYSTEM_EXEMPT_FROM_FORCED_APP_STANDBY =
+            "android:system_exempt_from_forced_app_standby";
+
     /** {@link #sAppOpsToNote} not initialized yet for this op */
     private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0;
     /** Should not collect noting of this app-op in {@link #sAppOpsToNote} */
@@ -1933,6 +2007,8 @@
             OP_SCHEDULE_EXACT_ALARM,
             OP_MANAGE_MEDIA,
             OP_TURN_SCREEN_ON,
+            OP_RUN_LONG_JOBS,
+            OP_READ_MEDIA_VISUAL_USER_SELECTED,
     };
 
     static final AppOpInfo[] sAppOpInfos = new AppOpInfo[]{
@@ -2312,7 +2388,19 @@
         new AppOpInfo.Builder(OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO,
                 OPSTR_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO,
                 "RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO").setDefaultMode(
-                AppOpsManager.MODE_ALLOWED).build()
+                AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_RUN_LONG_JOBS, OPSTR_RUN_LONG_JOBS, "RUN_LONG_JOBS")
+                .setPermission(Manifest.permission.RUN_LONG_JOBS).build(),
+            new AppOpInfo.Builder(OP_READ_MEDIA_VISUAL_USER_SELECTED,
+                    OPSTR_READ_MEDIA_VISUAL_USER_SELECTED, "READ_MEDIA_VISUAL_USER_SELECTED")
+                    .setPermission(Manifest.permission.READ_MEDIA_VISUAL_USER_SELECTED)
+                    .setDefaultMode(AppOpsManager.MODE_ALLOWED).build(),
+        new AppOpInfo.Builder(OP_SYSTEM_EXEMPT_FROM_APP_STANDBY,
+                OPSTR_SYSTEM_EXEMPT_FROM_APP_STANDBY,
+                "SYSTEM_EXEMPT_FROM_APP_STANDBY").build(),
+        new AppOpInfo.Builder(OP_SYSTEM_EXEMPT_FROM_FORCED_APP_STANDBY,
+                OPSTR_SYSTEM_EXEMPT_FROM_FORCED_APP_STANDBY,
+                "SYSTEM_EXEMPT_FROM_FORCED_APP_STANDBY").build()
     };
 
     /**
diff --git a/core/java/android/app/BroadcastOptions.java b/core/java/android/app/BroadcastOptions.java
index 48638d1..45d4458 100644
--- a/core/java/android/app/BroadcastOptions.java
+++ b/core/java/android/app/BroadcastOptions.java
@@ -636,6 +636,7 @@
      * @param broadcastIsInteractive
      * @see #isInteractiveBroadcast()
      */
+    @RequiresPermission(android.Manifest.permission.BROADCAST_OPTION_INTERACTIVE)
     public void setInteractiveBroadcast(boolean broadcastIsInteractive) {
         mIsInteractiveBroadcast = broadcastIsInteractive;
     }
diff --git a/core/java/android/app/SyncNotedAppOp.java b/core/java/android/app/SyncNotedAppOp.java
index f156b30..f674e88 100644
--- a/core/java/android/app/SyncNotedAppOp.java
+++ b/core/java/android/app/SyncNotedAppOp.java
@@ -56,7 +56,7 @@
      * The package this op applies to
      * @hide
      */
-    private final @NonNull String mPackageName;
+    private final @Nullable String mPackageName;
 
     /**
      * Native code relies on parcel ordering, do not change
@@ -64,7 +64,7 @@
      */
     @TestApi
     public SyncNotedAppOp(int opMode, @IntRange(from = 0L) int opCode,
-            @Nullable String attributionTag, @NonNull String packageName) {
+            @Nullable String attributionTag, @Nullable String packageName) {
         this.mOpCode = opCode;
         com.android.internal.util.AnnotationValidations.validate(
                 IntRange.class, null, mOpCode,
@@ -101,7 +101,7 @@
      * @hide
      */
     public SyncNotedAppOp(@IntRange(from = 0L) int opCode, @Nullable String attributionTag,
-            @NonNull String packageName) {
+            @Nullable String packageName) {
         this(AppOpsManager.MODE_IGNORED, opCode, attributionTag, packageName);
     }
 
@@ -152,7 +152,7 @@
      * @hide
      */
     @DataClass.Generated.Member
-    public @NonNull String getPackageName() {
+    public @Nullable String getPackageName() {
         return mPackageName;
     }
 
@@ -211,11 +211,12 @@
 
         byte flg = 0;
         if (mAttributionTag != null) flg |= 0x4;
+        if (mPackageName != null) flg |= 0x8;
         dest.writeByte(flg);
         dest.writeInt(mOpMode);
         dest.writeInt(mOpCode);
         if (mAttributionTag != null) dest.writeString(mAttributionTag);
-        dest.writeString(mPackageName);
+        if (mPackageName != null) dest.writeString(mPackageName);
     }
 
     @Override
@@ -233,7 +234,7 @@
         int opMode = in.readInt();
         int opCode = in.readInt();
         String attributionTag = (flg & 0x4) == 0 ? null : in.readString();
-        String packageName = in.readString();
+        String packageName = (flg & 0x8) == 0 ? null : in.readString();
 
         this.mOpMode = opMode;
         this.mOpCode = opCode;
@@ -243,8 +244,6 @@
                 "to", AppOpsManager._NUM_OP - 1);
         this.mAttributionTag = attributionTag;
         this.mPackageName = packageName;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mPackageName);
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -264,10 +263,10 @@
     };
 
     @DataClass.Generated(
-            time = 1643320427700L,
+            time = 1667247337573L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/app/SyncNotedAppOp.java",
-            inputSignatures = "private final  int mOpMode\nprivate final @android.annotation.IntRange int mOpCode\nprivate final @android.annotation.Nullable java.lang.String mAttributionTag\nprivate final @android.annotation.NonNull java.lang.String mPackageName\npublic @android.annotation.NonNull java.lang.String getOp()\npublic  int getOpMode()\nprivate  java.lang.String opCodeToString()\nclass SyncNotedAppOp extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true, genConstructor=false, genToString=true)")
+            inputSignatures = "private final  int mOpMode\nprivate final @android.annotation.IntRange int mOpCode\nprivate final @android.annotation.Nullable java.lang.String mAttributionTag\nprivate final @android.annotation.Nullable java.lang.String mPackageName\npublic @android.annotation.NonNull java.lang.String getOp()\npublic  int getOpMode()\nprivate  java.lang.String opCodeToString()\nclass SyncNotedAppOp extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genAidl=true, genConstructor=false, genToString=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/app/TEST_MAPPING b/core/java/android/app/TEST_MAPPING
index 0f26818..ef10c0b 100644
--- a/core/java/android/app/TEST_MAPPING
+++ b/core/java/android/app/TEST_MAPPING
@@ -115,6 +115,15 @@
             "file_patterns": ["(/|^)VoiceInteract[^/]*"]
         },
         {
+            "name": "CtsLocalVoiceInteraction",
+            "options": [
+                {
+                    "exclude-annotation": "androidx.test.filters.FlakyTest"
+                }
+            ],
+            "file_patterns": ["(/|^)VoiceInteract[^/]*"]
+        },
+        {
             "name": "CtsOsTestCases",
             "options": [
                 {
diff --git a/core/java/android/app/TaskInfo.java b/core/java/android/app/TaskInfo.java
index a2dc47d..5a2f261 100644
--- a/core/java/android/app/TaskInfo.java
+++ b/core/java/android/app/TaskInfo.java
@@ -458,7 +458,8 @@
                 && isVisible == that.isVisible
                 && isSleeping == that.isSleeping
                 && Objects.equals(mTopActivityLocusId, that.mTopActivityLocusId)
-                && parentTaskId == that.parentTaskId;
+                && parentTaskId == that.parentTaskId
+                && Objects.equals(topActivity, that.topActivity);
     }
 
     /**
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 95e9c22..5e15b0a 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -2456,7 +2456,12 @@
 
         public void waitForCompletion() {
             try {
-                mLatch.await(30, TimeUnit.SECONDS);
+                final boolean completed = mLatch.await(30, TimeUnit.SECONDS);
+                if (completed) {
+                    Log.d(TAG, "Wallpaper set completion.");
+                } else {
+                    Log.d(TAG, "Timeout waiting for wallpaper set completion!");
+                }
             } catch (InterruptedException e) {
                 // This might be legit: the crop may take a very long time. Don't sweat
                 // it in that case; we are okay with display lagging behind in order to
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index de19687..f4cee5a 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -3864,6 +3864,56 @@
     public static final String EXTRA_RESOURCE_IDS =
             "android.app.extra.RESOURCE_IDS";
 
+    /** Allow the user to choose whether to enable MTE on the device. */
+    public static final int MTE_NOT_CONTROLLED_BY_POLICY = 0;
+
+    /**
+     * Require that MTE be enabled on the device, if supported. Can be set by a device owner or a
+     * profile owner of an organization-owned managed profile.
+     */
+    public static final int MTE_ENABLED = 1;
+
+    /** Require that MTE be disabled on the device. Can be set by a device owner. */
+    public static final int MTE_DISABLED = 2;
+
+    /** @hide */
+    @IntDef(
+            prefix = {"MTE_"},
+            value = {MTE_ENABLED, MTE_DISABLED, MTE_NOT_CONTROLLED_BY_POLICY})
+    @Retention(RetentionPolicy.SOURCE)
+    public static @interface MtePolicy {}
+
+    /**
+     * Set MTE policy for device. MTE_ENABLED does not necessarily enable MTE if set on a device
+     * that does not support MTE.
+     *
+     * The default policy is MTE_NOT_CONTROLLED_BY_POLICY.
+     *
+     * Memory Tagging Extension (MTE) is a CPU extension that allows to protect against certain
+     * classes of security problems at a small runtime performance cost overhead.
+     *
+     * @param policy the policy to be set
+     */
+    public void setMtePolicy(@MtePolicy int policy) {
+        // TODO(b/244290023): implement
+        // This is SecurityException to temporarily make ParentProfileTest happy.
+        // This is not used.
+        throw new SecurityException("not implemented");
+    }
+
+    /**
+     * Get currently set MTE policy. This is not necessarily the same as the state of MTE on the
+     * device, as the device might not support MTE.
+     *
+     * @return the currently set policy
+     */
+    public @MtePolicy int getMtePolicy() {
+        // TODO(b/244290023): implement
+        // This is SecurityException to temporarily make ParentProfileTest happy.
+        // This is not used.
+        throw new SecurityException("not implemented");
+    }
+
     /**
      * This object is a single place to tack on invalidation and disable calls.  All
      * binder caches in this class derive from this Config, so all can be invalidated or
diff --git a/core/java/android/app/backup/BackupRestoreEventLogger.java b/core/java/android/app/backup/BackupRestoreEventLogger.java
index b789b38..760c6f0 100644
--- a/core/java/android/app/backup/BackupRestoreEventLogger.java
+++ b/core/java/android/app/backup/BackupRestoreEventLogger.java
@@ -19,10 +19,15 @@
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.util.Slog;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
-import java.util.Collections;
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.ArrayList;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -38,6 +43,8 @@
  * @hide
  */
 public class BackupRestoreEventLogger {
+    private static final String TAG = "BackupRestoreEventLogger";
+
     /**
      * Max number of unique data types for which an instance of this logger can store info. Attempts
      * to use more distinct data type values will be rejected.
@@ -72,6 +79,8 @@
     public @interface BackupRestoreError {}
 
     private final int mOperationType;
+    private final Map<String, DataTypeResult> mResults = new HashMap<>();
+    private final MessageDigest mHashDigest;
 
     /**
      * @param operationType type of the operation for which logging will be performed. See
@@ -81,6 +90,14 @@
      */
     public BackupRestoreEventLogger(@OperationType int operationType) {
         mOperationType = operationType;
+
+        MessageDigest hashDigest = null;
+        try {
+            hashDigest = MessageDigest.getInstance("SHA-256");
+        } catch (NoSuchAlgorithmException e) {
+            Slog.w("Couldn't create MessageDigest for hash computation", e);
+        }
+        mHashDigest = hashDigest;
     }
 
     /**
@@ -98,7 +115,7 @@
      * @return boolean, indicating whether the log has been accepted.
      */
     public boolean logItemsBackedUp(@NonNull @BackupRestoreDataType String dataType, int count) {
-        return true;
+        return logSuccess(OperationType.BACKUP, dataType, count);
     }
 
     /**
@@ -118,7 +135,7 @@
      */
     public boolean logItemsBackupFailed(@NonNull @BackupRestoreDataType String dataType, int count,
             @Nullable @BackupRestoreError String error) {
-        return true;
+        return logFailure(OperationType.BACKUP, dataType, count, error);
     }
 
     /**
@@ -139,7 +156,7 @@
      */
     public boolean logBackupMetaData(@NonNull @BackupRestoreDataType String dataType,
             @NonNull String metaData) {
-        return true;
+        return logMetaData(OperationType.BACKUP, dataType, metaData);
     }
 
     /**
@@ -159,7 +176,7 @@
      * @return boolean, indicating whether the log has been accepted.
      */
     public boolean logItemsRestored(@NonNull @BackupRestoreDataType String dataType, int count) {
-        return true;
+        return logSuccess(OperationType.RESTORE, dataType, count);
     }
 
     /**
@@ -181,7 +198,7 @@
      */
     public boolean logItemsRestoreFailed(@NonNull @BackupRestoreDataType String dataType, int count,
             @Nullable @BackupRestoreError String error) {
-        return true;
+        return logFailure(OperationType.RESTORE, dataType, count, error);
     }
 
     /**
@@ -204,7 +221,7 @@
      */
     public boolean logRestoreMetadata(@NonNull @BackupRestoreDataType String dataType,
             @NonNull  String metadata) {
-        return true;
+        return logMetaData(OperationType.RESTORE, dataType, metadata);
     }
 
     /**
@@ -214,7 +231,7 @@
      * @hide
      */
     public List<DataTypeResult> getLoggingResults() {
-        return Collections.emptyList();
+        return new ArrayList<>(mResults.values());
     }
 
     /**
@@ -227,22 +244,97 @@
         return mOperationType;
     }
 
+    private boolean logSuccess(@OperationType int operationType,
+            @BackupRestoreDataType String dataType, int count) {
+        DataTypeResult dataTypeResult = getDataTypeResult(operationType, dataType);
+        if (dataTypeResult == null) {
+            return false;
+        }
+
+        dataTypeResult.mSuccessCount += count;
+        mResults.put(dataType, dataTypeResult);
+
+        return true;
+    }
+
+    private boolean logFailure(@OperationType int operationType,
+            @NonNull @BackupRestoreDataType String dataType, int count,
+            @Nullable @BackupRestoreError String error) {
+        DataTypeResult dataTypeResult = getDataTypeResult(operationType, dataType);
+        if (dataTypeResult == null) {
+            return false;
+        }
+
+        dataTypeResult.mFailCount += count;
+        if (error != null) {
+            dataTypeResult.mErrors.merge(error, count, Integer::sum);
+        }
+
+        return true;
+    }
+
+    private boolean logMetaData(@OperationType int operationType,
+            @NonNull @BackupRestoreDataType String dataType, @NonNull String metaData) {
+        if (mHashDigest == null) {
+            return false;
+        }
+        DataTypeResult dataTypeResult = getDataTypeResult(operationType, dataType);
+        if (dataTypeResult == null) {
+            return false;
+        }
+
+        dataTypeResult.mMetadataHash = getMetaDataHash(metaData);
+
+        return true;
+    }
+
+    /**
+     * Get the result container for the given data type.
+     *
+     * @return {@code DataTypeResult} object corresponding to the given {@code dataType} or
+     *         {@code null} if the logger can't accept logs for the given data type.
+     */
+    @Nullable
+    private DataTypeResult getDataTypeResult(@OperationType int operationType,
+            @BackupRestoreDataType String dataType) {
+        if (operationType != mOperationType) {
+            // Operation type for which we're trying to record logs doesn't match the operation
+            // type for which this logger instance was created.
+            Slog.d(TAG, "Operation type mismatch: logger created for " + mOperationType
+                    + ", trying to log for " + operationType);
+            return null;
+        }
+
+        if (!mResults.containsKey(dataType)) {
+            if (mResults.keySet().size() == DATA_TYPES_ALLOWED) {
+                // This is a new data type and we're already at capacity.
+                Slog.d(TAG, "Logger is full, ignoring new data type");
+                return null;
+            }
+
+            mResults.put(dataType,  new DataTypeResult(dataType));
+        }
+
+        return mResults.get(dataType);
+    }
+
+    private byte[] getMetaDataHash(String metaData) {
+        return mHashDigest.digest(metaData.getBytes(StandardCharsets.UTF_8));
+    }
+
     /**
      * Encapsulate logging results for a single data type.
      */
     public static class DataTypeResult {
         @BackupRestoreDataType
         private final String mDataType;
-        private final int mSuccessCount;
-        private final Map<String, Integer> mErrors;
-        private final byte[] mMetadataHash;
+        private int mSuccessCount;
+        private int mFailCount;
+        private final Map<String, Integer> mErrors = new HashMap<>();
+        private byte[] mMetadataHash;
 
-        public DataTypeResult(String dataType, int successCount,
-                Map<String, Integer> errors, byte[] metadataHash) {
+        public DataTypeResult(String dataType) {
             mDataType = dataType;
-            mSuccessCount = successCount;
-            mErrors = errors;
-            mMetadataHash = metadataHash;
         }
 
         @NonNull
@@ -260,6 +352,13 @@
         }
 
         /**
+         * @return number of items of the given data type that have failed to back up or restore.
+         */
+        public int getFailCount() {
+            return mFailCount;
+        }
+
+        /**
          * @return mapping of {@link BackupRestoreError} to the count of items that are affected by
          *         the error.
          */
diff --git a/core/java/android/companion/AssociatedDevice.java b/core/java/android/companion/AssociatedDevice.java
index 3758cdb..a833661 100644
--- a/core/java/android/companion/AssociatedDevice.java
+++ b/core/java/android/companion/AssociatedDevice.java
@@ -16,6 +16,7 @@
 
 package android.companion;
 
+import android.bluetooth.BluetoothDevice;
 import android.os.Parcel;
 import android.os.Parcelable;
 
@@ -23,19 +24,14 @@
 import androidx.annotation.Nullable;
 
 /**
- * Loose wrapper around device parcelable. Device can be one of three types:
+ * Container for device info from an association that is not self-managed.
+ * Device can be one of three types:
  *
  * <ul>
  *     <li>for classic Bluetooth - {@link android.bluetooth.BluetoothDevice}</li>
  *     <li>for Bluetooth LE - {@link android.bluetooth.le.ScanResult}</li>
  *     <li>for WiFi - {@link android.net.wifi.ScanResult}</li>
  * </ul>
- *
- * This class serves as temporary wrapper to deliver a loosely-typed parcelable object from
- * {@link com.android.companiondevicemanager.CompanionDeviceActivity} to the Companion app,
- * and should only be used internally.
- *
- * @hide
  */
 public final class AssociatedDevice implements Parcelable {
     private static final int CLASSIC_BLUETOOTH = 0;
@@ -44,6 +40,7 @@
 
     @NonNull private final Parcelable mDevice;
 
+    /** @hide */
     public AssociatedDevice(@NonNull Parcelable device) {
         mDevice = device;
     }
@@ -54,11 +51,39 @@
     }
 
     /**
-     * Return device info. Cast to expected device type.
+     * Return bluetooth device info. Null if associated device is not a bluetooth device.
+     * @return Remote bluetooth device details containing MAC address.
      */
-    @NonNull
-    public Parcelable getDevice() {
-        return mDevice;
+    @Nullable
+    public BluetoothDevice getBluetoothDevice() {
+        if (mDevice instanceof BluetoothDevice) {
+            return (BluetoothDevice) mDevice;
+        }
+        return null;
+    }
+
+    /**
+     * Return bluetooth LE device info. Null if associated device is not a BLE device.
+     * @return BLE scan result containing details of detected BLE device.
+     */
+    @Nullable
+    public android.bluetooth.le.ScanResult getBleDevice() {
+        if (mDevice instanceof android.bluetooth.le.ScanResult) {
+            return (android.bluetooth.le.ScanResult) mDevice;
+        }
+        return null;
+    }
+
+    /**
+     * Return Wi-Fi device info. Null if associated device is not a Wi-Fi device.
+     * @return Wi-Fi scan result containing details of detected access point.
+     */
+    @Nullable
+    public android.net.wifi.ScanResult getWifiDevice() {
+        if (mDevice instanceof android.net.wifi.ScanResult) {
+            return (android.net.wifi.ScanResult) mDevice;
+        }
+        return null;
     }
 
     @Override
diff --git a/core/java/android/companion/AssociationInfo.java b/core/java/android/companion/AssociationInfo.java
index 93964b3..5fd39fe 100644
--- a/core/java/android/companion/AssociationInfo.java
+++ b/core/java/android/companion/AssociationInfo.java
@@ -164,20 +164,19 @@
 
     /**
      * Companion device that was associated. Note that this field is not persisted across sessions.
-     *
-     * Cast to expected device type before use:
+     * Device can be one of the following types:
      *
      * <ul>
-     *     <li>for classic Bluetooth - {@link android.bluetooth.BluetoothDevice}</li>
-     *     <li>for Bluetooth LE - {@link android.bluetooth.le.ScanResult}</li>
-     *     <li>for WiFi - {@link android.net.wifi.ScanResult}</li>
+     *     <li>for classic Bluetooth - {@link AssociatedDevice#getBluetoothDevice()}</li>
+     *     <li>for Bluetooth LE - {@link AssociatedDevice#getBleDevice()}</li>
+     *     <li>for WiFi - {@link AssociatedDevice#getWifiDevice()}</li>
      * </ul>
      *
      * @return the companion device that was associated, or {@code null} if the device is
-     *         self-managed.
+     *         self-managed or this association info was retrieved from persistent storage.
      */
-    public @Nullable Parcelable getAssociatedDevice() {
-        return mAssociatedDevice == null ? null : mAssociatedDevice.getDevice();
+    public @Nullable AssociatedDevice getAssociatedDevice() {
+        return mAssociatedDevice;
     }
 
     /**
diff --git a/core/java/android/content/AttributionSource.java b/core/java/android/content/AttributionSource.java
index b0c6cbc..e981581 100644
--- a/core/java/android/content/AttributionSource.java
+++ b/core/java/android/content/AttributionSource.java
@@ -29,6 +29,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.Process;
+import android.os.UserHandle;
 import android.permission.PermissionManager;
 import android.util.ArraySet;
 
@@ -297,7 +298,7 @@
     public boolean checkCallingUid() {
         final int callingUid = Binder.getCallingUid();
         if (callingUid != Process.ROOT_UID
-                && callingUid != Process.SYSTEM_UID
+                && UserHandle.getAppId(callingUid) != Process.SYSTEM_UID
                 && callingUid != mAttributionSourceState.uid) {
             return false;
         }
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index cbc1789..1df0fa8 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -5202,6 +5202,15 @@
     public static final String DROPBOX_SERVICE = "dropbox";
 
     /**
+     * System service name for BackgroundInstallControlService. This service supervises the MBAs
+     * on device and provides the related metadata of the MBAs.
+     *
+     * @hide
+     */
+    @SuppressLint("ServiceName")
+    public static final String BACKGROUND_INSTALL_CONTROL_SERVICE = "background_install_control";
+
+    /**
      * System service name for BinaryTransparencyService. This is used to retrieve measurements
      * pertaining to various pre-installed and system binaries on device for the purposes of
      * providing transparency to the user.
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl b/core/java/android/content/pm/IBackgroundInstallControlService.aidl
similarity index 67%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
copy to core/java/android/content/pm/IBackgroundInstallControlService.aidl
index 1550ab3..c8e7cae 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
+++ b/core/java/android/content/pm/IBackgroundInstallControlService.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,6 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.systemui.shared.system;
+package android.content.pm;
 
-parcelable RemoteTransitionCompat;
+import android.content.pm.ParceledListSlice;
+
+/**
+ * {@hide}
+ */
+interface IBackgroundInstallControlService {
+    ParceledListSlice getBackgroundInstalledPackages(long flags, int userId);
+}
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index d2fb1fb..d7686e2 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -3346,7 +3346,7 @@
         /**
          * The label representing the app to be installed.
          */
-        private final @NonNull String mLabel;
+        private final @NonNull CharSequence mLabel;
         /**
          * The locale of the app label being used.
          */
@@ -3388,7 +3388,7 @@
         @DataClass.Generated.Member
         public PreapprovalDetails(
                 @Nullable Bitmap icon,
-                @NonNull String label,
+                @NonNull CharSequence label,
                 @NonNull ULocale locale,
                 @NonNull String packageName) {
             this.mIcon = icon;
@@ -3417,7 +3417,7 @@
          * The label representing the app to be installed.
          */
         @DataClass.Generated.Member
-        public @NonNull String getLabel() {
+        public @NonNull CharSequence getLabel() {
             return mLabel;
         }
 
@@ -3461,7 +3461,7 @@
             if (mIcon != null) flg |= 0x1;
             dest.writeByte(flg);
             if (mIcon != null) mIcon.writeToParcel(dest, flags);
-            dest.writeString8(mLabel);
+            dest.writeCharSequence(mLabel);
             dest.writeString8(mLocale.toString());
             dest.writeString8(mPackageName);
         }
@@ -3479,7 +3479,7 @@
 
             byte flg = in.readByte();
             Bitmap icon = (flg & 0x1) == 0 ? null : Bitmap.CREATOR.createFromParcel(in);
-            String label = in.readString8();
+            CharSequence label = (CharSequence) in.readCharSequence();
             ULocale locale = new ULocale(in.readString8());
             String packageName = in.readString8();
 
@@ -3519,7 +3519,7 @@
         public static final class Builder {
 
             private @Nullable Bitmap mIcon;
-            private @NonNull String mLabel;
+            private @NonNull CharSequence mLabel;
             private @NonNull ULocale mLocale;
             private @NonNull String mPackageName;
 
@@ -3545,7 +3545,7 @@
              * The label representing the app to be installed.
              */
             @DataClass.Generated.Member
-            public @NonNull Builder setLabel(@NonNull String value) {
+            public @NonNull Builder setLabel(@NonNull CharSequence value) {
                 checkNotUsed();
                 mBuilderFieldsSet |= 0x2;
                 mLabel = value;
@@ -3596,10 +3596,10 @@
         }
 
         @DataClass.Generated(
-                time = 1664257135109L,
+                time = 1666748098353L,
                 codegenVersion = "1.0.23",
                 sourceFile = "frameworks/base/core/java/android/content/pm/PackageInstaller.java",
-                inputSignatures = "private final @android.annotation.Nullable android.graphics.Bitmap mIcon\nprivate final @android.annotation.NonNull java.lang.String mLabel\nprivate final @android.annotation.NonNull android.icu.util.ULocale mLocale\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nclass PreapprovalDetails extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genHiddenConstructor=true, genBuilder=true, genToString=true)")
+                inputSignatures = "private final @android.annotation.Nullable android.graphics.Bitmap mIcon\nprivate final @android.annotation.NonNull java.lang.CharSequence mLabel\nprivate final @android.annotation.NonNull android.icu.util.ULocale mLocale\nprivate final @android.annotation.NonNull java.lang.String mPackageName\nclass PreapprovalDetails extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genParcelable=true, genHiddenConstructor=true, genBuilder=true, genToString=true)")
         @Deprecated
         private void __metadata() {}
 
diff --git a/core/java/android/content/pm/UserPackage.java b/core/java/android/content/pm/UserPackage.java
new file mode 100644
index 0000000..e75f551
--- /dev/null
+++ b/core/java/android/content/pm/UserPackage.java
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content.pm;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.os.Process;
+import android.os.UserHandle;
+import android.util.SparseArrayMap;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.ArrayUtils;
+
+import java.util.Objects;
+
+/**
+ * POJO to represent a package for a specific user ID.
+ *
+ * @hide
+ */
+public final class UserPackage {
+    @UserIdInt
+    public final int userId;
+    public final String packageName;
+
+    @GuardedBy("sCache")
+    private static final SparseArrayMap<String, UserPackage> sCache = new SparseArrayMap<>();
+
+    private static final Object sUserIdLock = new Object();
+    private static final class NoPreloadHolder {
+        /** Set of userIDs to cache objects for. */
+        @GuardedBy("sUserIdLock")
+        private static int[] sUserIds = new int[]{UserHandle.getUserId(Process.myUid())};
+    }
+
+    private UserPackage(int userId, String packageName) {
+        this.userId = userId;
+        this.packageName = packageName;
+    }
+
+    @Override
+    public String toString() {
+        return "<" + userId + ">" + packageName;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj instanceof UserPackage) {
+            UserPackage other = (UserPackage) obj;
+            return userId == other.userId && Objects.equals(packageName, other.packageName);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 0;
+        result = 31 * result + userId;
+        result = 31 * result + packageName.hashCode();
+        return result;
+    }
+
+    /** Return an instance of this class representing the given userId + packageName combination. */
+    @NonNull
+    public static UserPackage of(@UserIdInt int userId, @NonNull String packageName) {
+        synchronized (sUserIdLock) {
+            if (!ArrayUtils.contains(NoPreloadHolder.sUserIds, userId)) {
+                // Don't cache objects for invalid userIds.
+                return new UserPackage(userId, packageName);
+            }
+        }
+        synchronized (sCache) {
+            UserPackage up = sCache.get(userId, packageName);
+            if (up == null) {
+                packageName = packageName.intern();
+                up = new UserPackage(userId, packageName);
+                sCache.add(userId, packageName, up);
+            }
+            return up;
+        }
+    }
+
+    /** Remove the specified app from the cache. */
+    public static void removeFromCache(@UserIdInt int userId, @NonNull String packageName) {
+        synchronized (sCache) {
+            sCache.delete(userId, packageName);
+        }
+    }
+
+    /** Indicate the list of valid user IDs on the device. */
+    public static void setValidUserIds(@NonNull int[] userIds) {
+        userIds = userIds.clone();
+        synchronized (sUserIdLock) {
+            NoPreloadHolder.sUserIds = userIds;
+        }
+        synchronized (sCache) {
+            for (int u = sCache.numMaps() - 1; u >= 0; --u) {
+                final int userId = sCache.keyAt(u);
+                // Not holding sUserIdLock is intentional here. We don't modify the elements within
+                // the array and so even if this method is called multiple times with different sets
+                // of user IDs, we want to adjust the cache based on each new array.
+                if (!ArrayUtils.contains(userIds, userId)) {
+                    sCache.deleteAt(u);
+                }
+            }
+        }
+    }
+}
diff --git a/core/java/android/content/pm/UserProperties.java b/core/java/android/content/pm/UserProperties.java
index 645a1ac..80f3264 100644
--- a/core/java/android/content/pm/UserProperties.java
+++ b/core/java/android/content/pm/UserProperties.java
@@ -43,17 +43,20 @@
     // Attribute strings for reading/writing properties to/from XML.
     private static final String ATTR_SHOW_IN_LAUNCHER = "showInLauncher";
     private static final String ATTR_START_WITH_PARENT = "startWithParent";
+    private static final String ATTR_SHOW_IN_SETTINGS = "showInSettings";
 
     /** Index values of each property (to indicate whether they are present in this object). */
     @IntDef(prefix = "INDEX_", value = {
             INDEX_SHOW_IN_LAUNCHER,
             INDEX_START_WITH_PARENT,
+            INDEX_SHOW_IN_SETTINGS,
     })
     @Retention(RetentionPolicy.SOURCE)
     private @interface PropertyIndex {
     }
     private static final int INDEX_SHOW_IN_LAUNCHER = 0;
     private static final int INDEX_START_WITH_PARENT = 1;
+    private static final int INDEX_SHOW_IN_SETTINGS = 2;
     /** A bit set, mapping each PropertyIndex to whether it is present (1) or absent (0). */
     private long mPropertiesPresent = 0;
 
@@ -87,6 +90,37 @@
     public static final int SHOW_IN_LAUNCHER_NO = 2;
 
     /**
+     * Possible values for whether or how to show this user in the Settings app.
+     * @hide
+     */
+    @IntDef(prefix = "SHOW_IN_SETTINGS_", value = {
+            SHOW_IN_SETTINGS_WITH_PARENT,
+            SHOW_IN_SETTINGS_SEPARATE,
+            SHOW_IN_SETTINGS_NO,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ShowInSettings {
+    }
+    /**
+     * Suggests that the Settings app should show this user's apps in the main tab.
+     * That is, either this user is a full user, so its apps should be presented accordingly, or, if
+     * this user is a profile, then its apps should be shown alongside its parent's apps.
+     * @hide
+     */
+    public static final int SHOW_IN_SETTINGS_WITH_PARENT = 0;
+    /**
+     * Suggests that the Settings app should show this user's apps, but separately from the apps of
+     * this user's parent.
+     * @hide
+     */
+    public static final int SHOW_IN_SETTINGS_SEPARATE = 1;
+    /**
+     * Suggests that the Settings app should not show this user.
+     * @hide
+     */
+    public static final int SHOW_IN_SETTINGS_NO = 2;
+
+    /**
      * Reference to the default user properties for this user's user type.
      * <li>If non-null, then any absent property will use the default property from here instead.
      * <li>If null, then any absent property indicates that the caller lacks permission to see it,
@@ -130,6 +164,7 @@
         }
         if (hasManagePermission) {
             // Add items that require MANAGE_USERS or stronger.
+            setShowInSettings(orig.getShowInSettings());
         }
         if (hasQueryOrManagePermission) {
             // Add items that require QUERY_USERS or stronger.
@@ -183,6 +218,33 @@
     private @ShowInLauncher int mShowInLauncher;
 
     /**
+     * Returns whether, and how, a user should be shown in the Settings app.
+     * This is generally inapplicable for non-profile users.
+     *
+     * Possible return values include
+     *    {@link #SHOW_IN_SETTINGS_WITH_PARENT}},
+     *    {@link #SHOW_IN_SETTINGS_SEPARATE},
+     *    and {@link #SHOW_IN_SETTINGS_NO}.
+     *
+     * <p> The caller must have {@link android.Manifest.permission#MANAGE_USERS} to query this
+     * property.
+     *
+     * @return whether, and how, a profile should be shown in the Settings.
+     * @hide
+     */
+    public @ShowInSettings int getShowInSettings() {
+        if (isPresent(INDEX_SHOW_IN_SETTINGS)) return mShowInSettings;
+        if (mDefaultProperties != null) return mDefaultProperties.mShowInSettings;
+        throw new SecurityException("You don't have permission to query mShowInSettings");
+    }
+    /** @hide */
+    public void setShowInSettings(@ShowInSettings int val) {
+        this.mShowInSettings = val;
+        setPresent(INDEX_SHOW_IN_SETTINGS);
+    }
+    private @ShowInSettings int mShowInSettings;
+
+    /**
      * Returns whether a profile should be started when its parent starts (unless in quiet mode).
      * This only applies for users that have parents (i.e. for profiles).
      * @hide
@@ -206,6 +268,7 @@
                 + "mPropertiesPresent=" + Long.toBinaryString(mPropertiesPresent)
                 + ", mShowInLauncher=" + getShowInLauncher()
                 + ", mStartWithParent=" + getStartWithParent()
+                + ", mShowInSettings=" + getShowInSettings()
                 + "}";
     }
 
@@ -219,6 +282,7 @@
         pw.println(prefix + "    mPropertiesPresent=" + Long.toBinaryString(mPropertiesPresent));
         pw.println(prefix + "    mShowInLauncher=" + getShowInLauncher());
         pw.println(prefix + "    mStartWithParent=" + getStartWithParent());
+        pw.println(prefix + "    mShowInSettings=" + getShowInSettings());
     }
 
     /**
@@ -258,6 +322,8 @@
                 case ATTR_START_WITH_PARENT:
                     setStartWithParent(parser.getAttributeBoolean(i));
                     break;
+                case ATTR_SHOW_IN_SETTINGS:
+                    setShowInSettings(parser.getAttributeInt(i));
                 default:
                     Slog.w(LOG_TAG, "Skipping unknown property " + attributeName);
             }
@@ -281,6 +347,9 @@
         if (isPresent(INDEX_START_WITH_PARENT)) {
             serializer.attributeBoolean(null, ATTR_START_WITH_PARENT, mStartWithParent);
         }
+        if (isPresent(INDEX_SHOW_IN_SETTINGS)) {
+            serializer.attributeInt(null, ATTR_SHOW_IN_SETTINGS, mShowInSettings);
+        }
     }
 
     // For use only with an object that has already had any permission-lacking fields stripped out.
@@ -289,6 +358,7 @@
         dest.writeLong(mPropertiesPresent);
         dest.writeInt(mShowInLauncher);
         dest.writeBoolean(mStartWithParent);
+        dest.writeInt(mShowInSettings);
     }
 
     /**
@@ -301,6 +371,7 @@
         mPropertiesPresent = source.readLong();
         mShowInLauncher = source.readInt();
         mStartWithParent = source.readBoolean();
+        mShowInSettings = source.readInt();
     }
 
     @Override
@@ -327,6 +398,7 @@
         // UserProperties fields and their default values.
         private @ShowInLauncher int mShowInLauncher = SHOW_IN_LAUNCHER_WITH_PARENT;
         private boolean mStartWithParent = false;
+        private @ShowInSettings int mShowInSettings = SHOW_IN_SETTINGS_WITH_PARENT;
 
         public Builder setShowInLauncher(@ShowInLauncher int showInLauncher) {
             mShowInLauncher = showInLauncher;
@@ -338,21 +410,30 @@
             return this;
         }
 
+        /** Sets the value for {@link #mShowInSettings} */
+        public Builder setShowInSettings(@ShowInSettings int showInSettings) {
+            mShowInSettings = showInSettings;
+            return this;
+        }
+
         /** Builds a UserProperties object with *all* values populated. */
         public UserProperties build() {
             return new UserProperties(
                     mShowInLauncher,
-                    mStartWithParent);
+                    mStartWithParent,
+                    mShowInSettings);
         }
     } // end Builder
 
     /** Creates a UserProperties with the given properties. Intended for building default values. */
     private UserProperties(
             @ShowInLauncher int showInLauncher,
-            boolean startWithParent) {
+            boolean startWithParent,
+            @ShowInSettings int showInSettings) {
 
         mDefaultProperties = null;
         setShowInLauncher(showInLauncher);
         setStartWithParent(startWithParent);
+        setShowInSettings(showInSettings);
     }
 }
diff --git a/core/java/android/credentials/Credential.java b/core/java/android/credentials/Credential.java
index fed2592..db89170 100644
--- a/core/java/android/credentials/Credential.java
+++ b/core/java/android/credentials/Credential.java
@@ -36,7 +36,8 @@
      *
      * @hide
      */
-    @NonNull public static final String TYPE_PASSWORD = "android.credentials.TYPE_PASSWORD";
+    @NonNull public static final String TYPE_PASSWORD_CREDENTIAL =
+            "android.credentials.TYPE_PASSWORD_CREDENTIAL";
 
     /**
      * The credential type.
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index b9cef0f..c9a0626 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -62,10 +62,10 @@
      * <p>The execution can potentially launch UI flows to collect user consent to using a
      * credential, display a picker when multiple credentials exist, etc.
      *
-     * @param request the request specifying type(s) of credentials to get from the user.
-     * @param cancellationSignal an optional signal that allows for cancelling this call.
-     * @param executor the callback will take place on this {@link Executor}.
-     * @param callback the callback invoked when the request succeeds or fails.
+     * @param request the request specifying type(s) of credentials to get from the user
+     * @param cancellationSignal an optional signal that allows for cancelling this call
+     * @param executor the callback will take place on this {@link Executor}
+     * @param callback the callback invoked when the request succeeds or fails
      */
     public void executeGetCredential(
             @NonNull GetCredentialRequest request,
@@ -85,7 +85,7 @@
         ICancellationSignal cancelRemote = null;
         try {
             cancelRemote = mService.executeGetCredential(request,
-                    new GetCredentialTransport(executor, callback));
+                    new GetCredentialTransport(executor, callback), mContext.getOpPackageName());
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
         }
@@ -101,10 +101,10 @@
      * <p>The execution can potentially launch UI flows to collect user consent to creating
      * or storing the new credential, etc.
      *
-     * @param request the request specifying type(s) of credentials to get from the user.
-     * @param cancellationSignal an optional signal that allows for cancelling this call.
-     * @param executor the callback will take place on this {@link Executor}.
-     * @param callback the callback invoked when the request succeeds or fails.
+     * @param request the request specifying type(s) of credentials to get from the user
+     * @param cancellationSignal an optional signal that allows for cancelling this call
+     * @param executor the callback will take place on this {@link Executor}
+     * @param callback the callback invoked when the request succeeds or fails
      */
     public void executeCreateCredential(
             @NonNull CreateCredentialRequest request,
@@ -124,7 +124,46 @@
         ICancellationSignal cancelRemote = null;
         try {
             cancelRemote = mService.executeCreateCredential(request,
-                    new CreateCredentialTransport(executor, callback));
+                    new CreateCredentialTransport(executor, callback),
+                    mContext.getOpPackageName());
+        } catch (RemoteException e) {
+            e.rethrowFromSystemServer();
+        }
+
+        if (cancellationSignal != null && cancelRemote != null) {
+            cancellationSignal.setRemote(cancelRemote);
+        }
+    }
+
+    /**
+     * Clears the current user credential session from all credential providers.
+     *
+     * <p>Usually invoked after your user signs out of your app so that they will not be
+     * automatically signed in the next time.
+     *
+     * @param cancellationSignal an optional signal that allows for cancelling this call
+     * @param executor the callback will take place on this {@link Executor}
+     * @param callback the callback invoked when the request succeeds or fails
+     *
+     * @hide
+     */
+    public void clearCredentialSession(
+            @Nullable CancellationSignal cancellationSignal,
+            @CallbackExecutor @NonNull Executor executor,
+            @NonNull OutcomeReceiver<Void, CredentialManagerException> callback) {
+        requireNonNull(executor, "executor must not be null");
+        requireNonNull(callback, "callback must not be null");
+
+        if (cancellationSignal != null && cancellationSignal.isCanceled()) {
+            Log.w(TAG, "executeCreateCredential already canceled");
+            return;
+        }
+
+        ICancellationSignal cancelRemote = null;
+        try {
+            cancelRemote = mService.clearCredentialSession(
+                    new ClearCredentialSessionTransport(executor, callback),
+                    mContext.getOpPackageName());
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
         }
@@ -183,4 +222,29 @@
                     () -> mCallback.onError(new CredentialManagerException(errorCode, message)));
         }
     }
+
+    private static class ClearCredentialSessionTransport
+            extends IClearCredentialSessionCallback.Stub {
+        // TODO: listen for cancellation to release callback.
+
+        private final Executor mExecutor;
+        private final OutcomeReceiver<Void, CredentialManagerException> mCallback;
+
+        private ClearCredentialSessionTransport(Executor executor,
+                OutcomeReceiver<Void, CredentialManagerException> callback) {
+            mExecutor = executor;
+            mCallback = callback;
+        }
+
+        @Override
+        public void onSuccess() {
+            mCallback.onResult(null);
+        }
+
+        @Override
+        public void onError(int errorCode, String message) {
+            mExecutor.execute(
+                    () -> mCallback.onError(new CredentialManagerException(errorCode, message)));
+        }
+    }
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl b/core/java/android/credentials/IClearCredentialSessionCallback.aidl
similarity index 66%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
copy to core/java/android/credentials/IClearCredentialSessionCallback.aidl
index 1550ab3..903e7f5 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
+++ b/core/java/android/credentials/IClearCredentialSessionCallback.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright 2022 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,6 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.systemui.shared.system;
+package android.credentials;
 
-parcelable RemoteTransitionCompat;
+/**
+ * Listener for clearCredentialSession request.
+ *
+ * @hide
+ */
+interface IClearCredentialSessionCallback {
+    oneway void onSuccess();
+    oneway void onError(int errorCode, String message);
+}
\ No newline at end of file
diff --git a/core/java/android/credentials/ICredentialManager.aidl b/core/java/android/credentials/ICredentialManager.aidl
index dcf7106..35688d7 100644
--- a/core/java/android/credentials/ICredentialManager.aidl
+++ b/core/java/android/credentials/ICredentialManager.aidl
@@ -18,6 +18,7 @@
 
 import android.credentials.CreateCredentialRequest;
 import android.credentials.GetCredentialRequest;
+import android.credentials.IClearCredentialSessionCallback;
 import android.credentials.ICreateCredentialCallback;
 import android.credentials.IGetCredentialCallback;
 import android.os.ICancellationSignal;
@@ -29,7 +30,9 @@
  */
 interface ICredentialManager {
 
-    @nullable ICancellationSignal executeGetCredential(in GetCredentialRequest request, in IGetCredentialCallback callback);
+    @nullable ICancellationSignal executeGetCredential(in GetCredentialRequest request, in IGetCredentialCallback callback, String callingPackage);
 
-    @nullable ICancellationSignal executeCreateCredential(in CreateCredentialRequest request, in ICreateCredentialCallback callback);
+    @nullable ICancellationSignal executeCreateCredential(in CreateCredentialRequest request, in ICreateCredentialCallback callback, String callingPackage);
+
+    @nullable ICancellationSignal clearCredentialSession(in IClearCredentialSessionCallback callback, String callingPackage);
 }
diff --git a/core/java/android/credentials/ui/CreateCredentialProviderData.java b/core/java/android/credentials/ui/CreateCredentialProviderData.java
new file mode 100644
index 0000000..98157d7
--- /dev/null
+++ b/core/java/android/credentials/ui/CreateCredentialProviderData.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials.ui;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.AnnotationValidations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Per-provider metadata and entries for the create-credential flow.
+ *
+ * @hide
+ */
+public class CreateCredentialProviderData extends ProviderData implements Parcelable {
+    @NonNull
+    private final List<Entry> mSaveEntries;
+    private final boolean mIsDefaultProvider;
+    @Nullable
+    private final Entry mRemoteEntry;
+
+    public CreateCredentialProviderData(
+            @NonNull String providerFlattenedComponentName, @NonNull List<Entry> saveEntries,
+            boolean isDefaultProvider, @Nullable Entry remoteEntry) {
+        super(providerFlattenedComponentName);
+        mSaveEntries = saveEntries;
+        mIsDefaultProvider = isDefaultProvider;
+        mRemoteEntry = remoteEntry;
+    }
+
+    @NonNull
+    public List<Entry> getSaveEntries() {
+        return mSaveEntries;
+    }
+
+    public boolean isDefaultProvider() {
+        return mIsDefaultProvider;
+    }
+
+    @Nullable
+    public Entry getRemoteEntry() {
+        return mRemoteEntry;
+    }
+
+    protected CreateCredentialProviderData(@NonNull Parcel in) {
+        super(in);
+
+        List<Entry> credentialEntries = new ArrayList<>();
+        in.readTypedList(credentialEntries, Entry.CREATOR);
+        mSaveEntries = credentialEntries;
+        AnnotationValidations.validate(NonNull.class, null, mSaveEntries);
+
+        mIsDefaultProvider = in.readBoolean();
+
+        Entry remoteEntry = in.readTypedObject(Entry.CREATOR);
+        mRemoteEntry = remoteEntry;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        dest.writeTypedList(mSaveEntries);
+        dest.writeBoolean(isDefaultProvider());
+        dest.writeTypedObject(mRemoteEntry, flags);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final @NonNull Creator<CreateCredentialProviderData> CREATOR =
+            new Creator<CreateCredentialProviderData>() {
+        @Override
+        public CreateCredentialProviderData createFromParcel(@NonNull Parcel in) {
+            return new CreateCredentialProviderData(in);
+        }
+
+        @Override
+        public CreateCredentialProviderData[] newArray(int size) {
+            return new CreateCredentialProviderData[size];
+        }
+    };
+
+    /**
+     * Builder for {@link CreateCredentialProviderData}.
+     *
+     * @hide
+     */
+    public static class Builder {
+        private @NonNull String mProviderFlattenedComponentName;
+        private @NonNull List<Entry> mSaveEntries = new ArrayList<>();
+        private boolean mIsDefaultProvider = false;
+        private @Nullable Entry mRemoteEntry = null;
+
+        /** Constructor with required properties. */
+        public Builder(@NonNull String providerFlattenedComponentName) {
+            mProviderFlattenedComponentName = providerFlattenedComponentName;
+        }
+
+        /** Sets the list of save credential entries to be displayed to the user. */
+        @NonNull
+        public Builder setSaveEntries(@NonNull List<Entry> credentialEntries) {
+            mSaveEntries = credentialEntries;
+            return this;
+        }
+
+        /** Sets whether this provider is the user's selected default provider. */
+        @NonNull
+        public Builder setIsDefaultProvider(boolean isDefaultProvider) {
+            mIsDefaultProvider = isDefaultProvider;
+            return this;
+        }
+
+        /** Builds a {@link CreateCredentialProviderData}. */
+        @NonNull
+        public CreateCredentialProviderData build() {
+            return new CreateCredentialProviderData(mProviderFlattenedComponentName,
+                    mSaveEntries, mIsDefaultProvider, mRemoteEntry);
+        }
+    }
+}
diff --git a/core/java/android/credentials/ui/DisabledProviderData.java b/core/java/android/credentials/ui/DisabledProviderData.java
new file mode 100644
index 0000000..73c8dbe
--- /dev/null
+++ b/core/java/android/credentials/ui/DisabledProviderData.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials.ui;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Metadata of a disabled provider.
+ *
+ * @hide
+ */
+public class DisabledProviderData extends ProviderData implements Parcelable {
+
+    public DisabledProviderData(
+            @NonNull String providerFlattenedComponentName) {
+        super(providerFlattenedComponentName);
+    }
+
+    protected DisabledProviderData(@NonNull Parcel in) {
+        super(in);
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final @NonNull Creator<DisabledProviderData> CREATOR = new Creator<>() {
+                @Override
+                public DisabledProviderData createFromParcel(@NonNull Parcel in) {
+                    return new DisabledProviderData(in);
+                }
+
+                @Override
+                public DisabledProviderData[] newArray(int size) {
+                    return new DisabledProviderData[size];
+                }
+    };
+}
diff --git a/core/java/android/credentials/ui/GetCredentialProviderData.java b/core/java/android/credentials/ui/GetCredentialProviderData.java
new file mode 100644
index 0000000..834f9825
--- /dev/null
+++ b/core/java/android/credentials/ui/GetCredentialProviderData.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.credentials.ui;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.AnnotationValidations;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Per-provider metadata and entries for the get-credential flow.
+ *
+ * @hide
+ */
+public class GetCredentialProviderData extends ProviderData implements Parcelable {
+    @NonNull
+    private final List<Entry> mCredentialEntries;
+    @NonNull
+    private final List<Entry> mActionChips;
+    @Nullable
+    private final Entry mAuthenticationEntry;
+    @Nullable
+    private final Entry mRemoteEntry;
+
+    public GetCredentialProviderData(
+            @NonNull String providerFlattenedComponentName, @NonNull List<Entry> credentialEntries,
+            @NonNull List<Entry> actionChips, @Nullable Entry authenticationEntry,
+            @Nullable Entry remoteEntry) {
+        super(providerFlattenedComponentName);
+        mCredentialEntries = credentialEntries;
+        mActionChips = actionChips;
+        mAuthenticationEntry = authenticationEntry;
+        mRemoteEntry = remoteEntry;
+    }
+
+    @NonNull
+    public List<Entry> getCredentialEntries() {
+        return mCredentialEntries;
+    }
+
+    @NonNull
+    public List<Entry> getActionChips() {
+        return mActionChips;
+    }
+
+    @Nullable
+    public Entry getAuthenticationEntry() {
+        return mAuthenticationEntry;
+    }
+
+    @Nullable
+    public Entry getRemoteEntry() {
+        return mRemoteEntry;
+    }
+
+    protected GetCredentialProviderData(@NonNull Parcel in) {
+        super(in);
+
+        List<Entry> credentialEntries = new ArrayList<>();
+        in.readTypedList(credentialEntries, Entry.CREATOR);
+        mCredentialEntries = credentialEntries;
+        AnnotationValidations.validate(NonNull.class, null, mCredentialEntries);
+
+        List<Entry> actionChips  = new ArrayList<>();
+        in.readTypedList(actionChips, Entry.CREATOR);
+        mActionChips = actionChips;
+        AnnotationValidations.validate(NonNull.class, null, mActionChips);
+
+        Entry authenticationEntry = in.readTypedObject(Entry.CREATOR);
+        mAuthenticationEntry = authenticationEntry;
+
+        Entry remoteEntry = in.readTypedObject(Entry.CREATOR);
+        mRemoteEntry = remoteEntry;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
+        dest.writeTypedList(mCredentialEntries);
+        dest.writeTypedList(mActionChips);
+        dest.writeTypedObject(mAuthenticationEntry, flags);
+        dest.writeTypedObject(mRemoteEntry, flags);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    public static final @NonNull Creator<GetCredentialProviderData> CREATOR =
+            new Creator<GetCredentialProviderData>() {
+        @Override
+        public GetCredentialProviderData createFromParcel(@NonNull Parcel in) {
+            return new GetCredentialProviderData(in);
+        }
+
+        @Override
+        public GetCredentialProviderData[] newArray(int size) {
+            return new GetCredentialProviderData[size];
+        }
+    };
+
+    /**
+     * Builder for {@link GetCredentialProviderData}.
+     *
+     * @hide
+     */
+    public static class Builder {
+        private @NonNull String mProviderFlattenedComponentName;
+        private @NonNull List<Entry> mCredentialEntries = new ArrayList<>();
+        private @NonNull List<Entry> mActionChips = new ArrayList<>();
+        private @Nullable Entry mAuthenticationEntry = null;
+        private @Nullable Entry mRemoteEntry = null;
+
+        /** Constructor with required properties. */
+        public Builder(@NonNull String providerFlattenedComponentName) {
+            mProviderFlattenedComponentName = providerFlattenedComponentName;
+        }
+
+        /** Sets the list of save / get credential entries to be displayed to the user. */
+        @NonNull
+        public Builder setCredentialEntries(@NonNull List<Entry> credentialEntries) {
+            mCredentialEntries = credentialEntries;
+            return this;
+        }
+
+        /** Sets the list of action chips to be displayed to the user. */
+        @NonNull
+        public Builder setActionChips(@NonNull List<Entry> actionChips) {
+            mActionChips = actionChips;
+            return this;
+        }
+
+        /** Sets the authentication entry to be displayed to the user. */
+        @NonNull
+        public Builder setAuthenticationEntry(@Nullable Entry authenticationEntry) {
+            mAuthenticationEntry = authenticationEntry;
+            return this;
+        }
+
+        /** Sets the remote entry to be displayed to the user. */
+        @NonNull
+        public Builder setRemoteEntry(@Nullable Entry remoteEntry) {
+            mRemoteEntry = remoteEntry;
+            return this;
+        }
+
+        /** Builds a {@link GetCredentialProviderData}. */
+        @NonNull
+        public GetCredentialProviderData build() {
+            return new GetCredentialProviderData(mProviderFlattenedComponentName,
+                    mCredentialEntries, mActionChips, mAuthenticationEntry, mRemoteEntry);
+        }
+    }
+}
diff --git a/core/java/android/credentials/ui/IntentFactory.java b/core/java/android/credentials/ui/IntentFactory.java
index 1b70ea4..4751696 100644
--- a/core/java/android/credentials/ui/IntentFactory.java
+++ b/core/java/android/credentials/ui/IntentFactory.java
@@ -30,15 +30,20 @@
  */
 public class IntentFactory {
     /** Generate a new launch intent to the . */
-    public static Intent newIntent(RequestInfo requestInfo,
-            ArrayList<ProviderData> providerDataList, ResultReceiver resultReceiver) {
+    public static Intent newIntent(
+            RequestInfo requestInfo,
+            ArrayList<ProviderData> enabledProviderDataList,
+            ArrayList<DisabledProviderData> disabledProviderDataList,
+            ResultReceiver resultReceiver) {
         Intent intent = new Intent();
         // TODO: define these as proper config strings.
         String activityName = "com.android.credentialmanager/.CredentialSelectorActivity";
         intent.setComponent(ComponentName.unflattenFromString(activityName));
 
         intent.putParcelableArrayListExtra(
-                ProviderData.EXTRA_PROVIDER_DATA_LIST, providerDataList);
+                ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST, enabledProviderDataList);
+        intent.putParcelableArrayListExtra(
+                ProviderData.EXTRA_DISABLED_PROVIDER_DATA_LIST, disabledProviderDataList);
         intent.putExtra(RequestInfo.EXTRA_REQUEST_INFO, requestInfo);
         intent.putExtra(Constants.EXTRA_RESULT_RECEIVER,
                 toIpcFriendlyResultReceiver(resultReceiver));
diff --git a/core/java/android/credentials/ui/ProviderData.java b/core/java/android/credentials/ui/ProviderData.java
index 35e12fa..eeaeb46 100644
--- a/core/java/android/credentials/ui/ProviderData.java
+++ b/core/java/android/credentials/ui/ProviderData.java
@@ -16,231 +16,62 @@
 
 package android.credentials.ui;
 
-import android.annotation.CurrentTimeMillisLong;
 import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.graphics.drawable.Icon;
 import android.os.Parcel;
 import android.os.Parcelable;
 
 import com.android.internal.util.AnnotationValidations;
 
-import java.util.ArrayList;
-import java.util.List;
-
 /**
- * Holds metadata and credential entries for a single provider.
+ * Super class for data structures that hold metadata and credential entries for a single provider.
  *
  * @hide
  */
-public class ProviderData implements Parcelable {
+public abstract class ProviderData implements Parcelable {
 
     /**
-     * The intent extra key for the list of {@code ProviderData} when launching the UX
-     * activities.
+     * The intent extra key for the list of {@code ProviderData} from active providers when
+     * launching the UX activities.
      */
-    public static final String EXTRA_PROVIDER_DATA_LIST =
-            "android.credentials.ui.extra.PROVIDER_DATA_LIST";
+    public static final String EXTRA_ENABLED_PROVIDER_DATA_LIST =
+            "android.credentials.ui.extra.ENABLED_PROVIDER_DATA_LIST";
+    /**
+     * The intent extra key for the list of {@code ProviderData} from disabled providers when
+     * launching the UX activities.
+     */
+    public static final String EXTRA_DISABLED_PROVIDER_DATA_LIST =
+            "android.credentials.ui.extra.DISABLED_PROVIDER_DATA_LIST";
 
     @NonNull
-    private final String mProviderId;
-    @NonNull
-    private final String mProviderDisplayName;
-    @NonNull
-    private final Icon mIcon;
-    @NonNull
-    private final List<Entry> mCredentialEntries;
-    @NonNull
-    private final List<Entry> mActionChips;
-    @Nullable
-    private final Entry mAuthenticationEntry;
-
-    private final @CurrentTimeMillisLong long mLastUsedTimeMillis;
+    private final String mProviderFlattenedComponentName;
 
     public ProviderData(
-            @NonNull String providerId, @NonNull String providerDisplayName,
-            @NonNull Icon icon, @NonNull List<Entry> credentialEntries,
-            @NonNull List<Entry> actionChips, @Nullable Entry authenticationEntry,
-            @CurrentTimeMillisLong long lastUsedTimeMillis) {
-        mProviderId = providerId;
-        mProviderDisplayName = providerDisplayName;
-        mIcon = icon;
-        mCredentialEntries = credentialEntries;
-        mActionChips = actionChips;
-        mAuthenticationEntry = authenticationEntry;
-        mLastUsedTimeMillis = lastUsedTimeMillis;
+            @NonNull String providerFlattenedComponentName) {
+        mProviderFlattenedComponentName = providerFlattenedComponentName;
     }
 
-    /** Returns the unique provider id. */
+    /**
+     * Returns provider component name.
+     * It also serves as the unique identifier for this provider.
+     */
     @NonNull
-    public String getProviderId() {
-        return mProviderId;
-    }
-
-    @NonNull
-    public String getProviderDisplayName() {
-        return mProviderDisplayName;
-    }
-
-    @NonNull
-    public Icon getIcon() {
-        return mIcon;
-    }
-
-    @NonNull
-    public List<Entry> getCredentialEntries() {
-        return mCredentialEntries;
-    }
-
-    @NonNull
-    public List<Entry> getActionChips() {
-        return mActionChips;
-    }
-
-    @Nullable
-    public Entry getAuthenticationEntry() {
-        return mAuthenticationEntry;
-    }
-
-    /** Returns the time when the provider was last used. */
-    public @CurrentTimeMillisLong long getLastUsedTimeMillis() {
-        return mLastUsedTimeMillis;
+    public String getProviderFlattenedComponentName() {
+        return mProviderFlattenedComponentName;
     }
 
     protected ProviderData(@NonNull Parcel in) {
-        String providerId = in.readString8();
-        mProviderId = providerId;
-        AnnotationValidations.validate(NonNull.class, null, mProviderId);
-
-        String providerDisplayName = in.readString8();
-        mProviderDisplayName = providerDisplayName;
-        AnnotationValidations.validate(NonNull.class, null, mProviderDisplayName);
-
-        Icon icon = in.readTypedObject(Icon.CREATOR);
-        mIcon = icon;
-        AnnotationValidations.validate(NonNull.class, null, mIcon);
-
-        List<Entry> credentialEntries = new ArrayList<>();
-        in.readTypedList(credentialEntries, Entry.CREATOR);
-        mCredentialEntries = credentialEntries;
-        AnnotationValidations.validate(NonNull.class, null, mCredentialEntries);
-
-        List<Entry> actionChips  = new ArrayList<>();
-        in.readTypedList(actionChips, Entry.CREATOR);
-        mActionChips = actionChips;
-        AnnotationValidations.validate(NonNull.class, null, mActionChips);
-
-        Entry authenticationEntry = in.readTypedObject(Entry.CREATOR);
-        mAuthenticationEntry = authenticationEntry;
-
-        long lastUsedTimeMillis = in.readLong();
-        mLastUsedTimeMillis = lastUsedTimeMillis;
+        String providerFlattenedComponentName = in.readString8();
+        mProviderFlattenedComponentName = providerFlattenedComponentName;
+        AnnotationValidations.validate(NonNull.class, null, mProviderFlattenedComponentName);
     }
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeString8(mProviderId);
-        dest.writeString8(mProviderDisplayName);
-        dest.writeTypedObject(mIcon, flags);
-        dest.writeTypedList(mCredentialEntries);
-        dest.writeTypedList(mActionChips);
-        dest.writeTypedObject(mAuthenticationEntry, flags);
-        dest.writeLong(mLastUsedTimeMillis);
+        dest.writeString8(mProviderFlattenedComponentName);
     }
 
     @Override
     public int describeContents() {
         return 0;
     }
-
-    public static final @NonNull Creator<ProviderData> CREATOR = new Creator<ProviderData>() {
-        @Override
-        public ProviderData createFromParcel(@NonNull Parcel in) {
-            return new ProviderData(in);
-        }
-
-        @Override
-        public ProviderData[] newArray(int size) {
-            return new ProviderData[size];
-        }
-    };
-
-    /**
-     * Builder for {@link ProviderData}.
-     *
-     * @hide
-     */
-    public static class Builder {
-        private @NonNull String mProviderId;
-        private @NonNull String mProviderDisplayName;
-        private @NonNull Icon mIcon;
-        private @NonNull List<Entry> mCredentialEntries = new ArrayList<>();
-        private @NonNull List<Entry> mActionChips = new ArrayList<>();
-        private @Nullable Entry mAuthenticationEntry = null;
-        private @CurrentTimeMillisLong long mLastUsedTimeMillis = 0L;
-
-        /** Constructor with required properties. */
-        public Builder(@NonNull String providerId, @NonNull String providerDisplayName,
-                @NonNull Icon icon) {
-            mProviderId = providerId;
-            mProviderDisplayName = providerDisplayName;
-            mIcon = icon;
-        }
-
-        /** Sets the unique provider id. */
-        @NonNull
-        public Builder setProviderId(@NonNull String providerId) {
-            mProviderId = providerId;
-            return this;
-        }
-
-        /** Sets the provider display name to be displayed to the user. */
-        @NonNull
-        public Builder setProviderDisplayName(@NonNull String providerDisplayName) {
-            mProviderDisplayName = providerDisplayName;
-            return this;
-        }
-
-        /** Sets the provider icon to be displayed to the user. */
-        @NonNull
-        public Builder setIcon(@NonNull Icon icon) {
-            mIcon = icon;
-            return this;
-        }
-
-        /** Sets the list of save / get credential entries to be displayed to the user. */
-        @NonNull
-        public Builder setCredentialEntries(@NonNull List<Entry> credentialEntries) {
-            mCredentialEntries = credentialEntries;
-            return this;
-        }
-
-        /** Sets the list of action chips to be displayed to the user. */
-        @NonNull
-        public Builder setActionChips(@NonNull List<Entry> actionChips) {
-            mActionChips = actionChips;
-            return this;
-        }
-
-        /** Sets the authentication entry to be displayed to the user. */
-        @NonNull
-        public Builder setAuthenticationEntry(@Nullable Entry authenticationEntry) {
-            mAuthenticationEntry = authenticationEntry;
-            return this;
-        }
-
-        /** Sets the time when the provider was last used. */
-        @NonNull
-        public Builder setLastUsedTimeMillis(@CurrentTimeMillisLong long lastUsedTimeMillis) {
-            mLastUsedTimeMillis = lastUsedTimeMillis;
-            return this;
-        }
-
-        /** Builds a {@link ProviderData}. */
-        @NonNull
-        public ProviderData build() {
-            return new ProviderData(mProviderId, mProviderDisplayName, mIcon, mCredentialEntries,
-                mActionChips, mAuthenticationEntry, mLastUsedTimeMillis);
-        }
-    }
 }
diff --git a/core/java/android/credentials/ui/RequestInfo.java b/core/java/android/credentials/ui/RequestInfo.java
index 619b08e..c3937b6 100644
--- a/core/java/android/credentials/ui/RequestInfo.java
+++ b/core/java/android/credentials/ui/RequestInfo.java
@@ -70,23 +70,23 @@
     private final boolean mIsFirstUsage;
 
     @NonNull
-    private final String mAppDisplayName;
+    private final String mAppPackageName;
 
     /** Creates new {@code RequestInfo} for a create-credential flow. */
     public static RequestInfo newCreateRequestInfo(
             @NonNull IBinder token, @NonNull CreateCredentialRequest createCredentialRequest,
-            boolean isFirstUsage, @NonNull String appDisplayName) {
+            boolean isFirstUsage, @NonNull String appPackageName) {
         return new RequestInfo(
-                token, TYPE_CREATE, isFirstUsage, appDisplayName,
+                token, TYPE_CREATE, isFirstUsage, appPackageName,
                 createCredentialRequest, null);
     }
 
     /** Creates new {@code RequestInfo} for a get-credential flow. */
     public static RequestInfo newGetRequestInfo(
             @NonNull IBinder token, @NonNull GetCredentialRequest getCredentialRequest,
-            boolean isFirstUsage, @NonNull String appDisplayName) {
+            boolean isFirstUsage, @NonNull String appPackageName) {
         return new RequestInfo(
-                token, TYPE_GET, isFirstUsage, appDisplayName,
+                token, TYPE_GET, isFirstUsage, appPackageName,
                 null, getCredentialRequest);
     }
 
@@ -115,8 +115,8 @@
 
     /** Returns the display name of the app that made this request. */
     @NonNull
-    public String getAppDisplayName() {
-        return mAppDisplayName;
+    public String getAppPackageName() {
+        return mAppPackageName;
     }
 
     /**
@@ -138,13 +138,13 @@
     }
 
     private RequestInfo(@NonNull IBinder token, @NonNull @RequestType String type,
-            boolean isFirstUsage, @NonNull String appDisplayName,
+            boolean isFirstUsage, @NonNull String appPackageName,
             @Nullable CreateCredentialRequest createCredentialRequest,
             @Nullable GetCredentialRequest getCredentialRequest) {
         mToken = token;
         mType = type;
         mIsFirstUsage = isFirstUsage;
-        mAppDisplayName = appDisplayName;
+        mAppPackageName = appPackageName;
         mCreateCredentialRequest = createCredentialRequest;
         mGetCredentialRequest = getCredentialRequest;
     }
@@ -153,7 +153,7 @@
         IBinder token = in.readStrongBinder();
         String type = in.readString8();
         boolean isFirstUsage = in.readBoolean();
-        String appDisplayName = in.readString8();
+        String appPackageName = in.readString8();
         CreateCredentialRequest createCredentialRequest =
                 in.readTypedObject(CreateCredentialRequest.CREATOR);
         GetCredentialRequest getCredentialRequest =
@@ -164,8 +164,8 @@
         mType = type;
         AnnotationValidations.validate(NonNull.class, null, mType);
         mIsFirstUsage = isFirstUsage;
-        mAppDisplayName = appDisplayName;
-        AnnotationValidations.validate(NonNull.class, null, mAppDisplayName);
+        mAppPackageName = appPackageName;
+        AnnotationValidations.validate(NonNull.class, null, mAppPackageName);
         mCreateCredentialRequest = createCredentialRequest;
         mGetCredentialRequest = getCredentialRequest;
     }
@@ -175,7 +175,7 @@
         dest.writeStrongBinder(mToken);
         dest.writeString8(mType);
         dest.writeBoolean(mIsFirstUsage);
-        dest.writeString8(mAppDisplayName);
+        dest.writeString8(mAppPackageName);
         dest.writeTypedObject(mCreateCredentialRequest, flags);
         dest.writeTypedObject(mGetCredentialRequest, flags);
     }
diff --git a/core/java/android/hardware/SensorPrivacyManager.java b/core/java/android/hardware/SensorPrivacyManager.java
index 99b58c9..535b551 100644
--- a/core/java/android/hardware/SensorPrivacyManager.java
+++ b/core/java/android/hardware/SensorPrivacyManager.java
@@ -73,6 +73,13 @@
             + ".extra.all_sensors";
 
     /**
+     * An extra containing the sensor type
+     * @hide
+     */
+    public static final String EXTRA_TOGGLE_TYPE = SensorPrivacyManager.class.getName()
+            + ".extra.toggle_type";
+
+    /**
      * Sensor constants which are used in {@link SensorPrivacyManager}
      */
     public static class Sensors {
diff --git a/core/java/android/hardware/camera2/params/DeviceStateSensorOrientationMap.java b/core/java/android/hardware/camera2/params/DeviceStateSensorOrientationMap.java
index b9a327b..d9ee561 100644
--- a/core/java/android/hardware/camera2/params/DeviceStateSensorOrientationMap.java
+++ b/core/java/android/hardware/camera2/params/DeviceStateSensorOrientationMap.java
@@ -204,7 +204,7 @@
          *
          */
         @SuppressLint("MissingGetterMatchingBuilder")
-        public @NonNull Builder addOrientationForState(long deviceState, long angle) {
+        public @NonNull Builder addOrientationForState(@DeviceState long deviceState, long angle) {
             if (angle % 90 != 0) {
                 throw new IllegalArgumentException("Sensor orientation not divisible by 90: "
                         + angle);
diff --git a/core/java/android/hardware/camera2/params/Face.java b/core/java/android/hardware/camera2/params/Face.java
index 1d9a5a3a..32688a72 100644
--- a/core/java/android/hardware/camera2/params/Face.java
+++ b/core/java/android/hardware/camera2/params/Face.java
@@ -17,6 +17,7 @@
 
 package android.hardware.camera2.params;
 
+import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.graphics.Point;
@@ -173,6 +174,7 @@
      * @see #SCORE_MAX
      * @see #SCORE_MIN
      */
+    @IntRange(from = SCORE_MIN, to = SCORE_MAX)
     public int getScore() {
         return mScore;
     }
@@ -377,7 +379,7 @@
          * @param score Confidence level between {@value #SCORE_MIN}-{@value #SCORE_MAX}.
          * @return This builder.
          */
-        public @NonNull Builder setScore(int score) {
+        public @NonNull Builder setScore(@IntRange(from = SCORE_MIN, to = SCORE_MAX) int score) {
             checkNotUsed();
             checkScore(score);
             mBuilderFieldsSet |= FIELD_SCORE;
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 5403f08..3c73eb6 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -918,6 +918,22 @@
         }
     }
 
+    /**
+     * @hide
+     */
+    @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+    public void setUdfpsOverlay(@NonNull IUdfpsOverlay controller) {
+        if (mService == null) {
+            Slog.w(TAG, "setUdfpsOverlay: no fingerprint service");
+            return;
+        }
+
+        try {
+            mService.setUdfpsOverlay(controller);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
 
     /**
      * Forwards BiometricStateListener to FingerprintService
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index 051e3a4..365a6b3 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -26,6 +26,7 @@
 import android.hardware.fingerprint.IFingerprintServiceReceiver;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.hardware.fingerprint.ISidefpsController;
+import android.hardware.fingerprint.IUdfpsOverlay;
 import android.hardware.fingerprint.Fingerprint;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import java.util.List;
@@ -201,6 +202,10 @@
     @EnforcePermission("USE_BIOMETRIC_INTERNAL")
     void setSidefpsController(in ISidefpsController controller);
 
+    // Sets the controller for managing the UDFPS overlay.
+    @EnforcePermission("USE_BIOMETRIC_INTERNAL")
+    void setUdfpsOverlay(in IUdfpsOverlay controller);
+
     // Registers BiometricStateListener.
     @EnforcePermission("USE_BIOMETRIC_INTERNAL")
     void registerBiometricStateListener(IBiometricStateListener listener);
diff --git a/core/java/android/hardware/fingerprint/IUdfpsHbmListener.aidl b/core/java/android/hardware/fingerprint/IUdfpsHbmListener.aidl
deleted file mode 100644
index 9c2aa66..0000000
--- a/core/java/android/hardware/fingerprint/IUdfpsHbmListener.aidl
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package android.hardware.fingerprint;
-
-/**
- * A listener for the high-brightness mode (HBM) transitions. This allows other components to
- * perform certain actions when the HBM is toggled on or off. For example, a display manager
- * implementation can subscribe to these events from UdfpsController and adjust the display's
- * refresh rate when the HBM is enabled.
- *
- * @hide
- */
-oneway interface IUdfpsHbmListener {
-    /**
-     * UdfpsController will call this method when the HBM is enabled.
-     *
-     * @param displayId The displayId for which the HBM is enabled. See
-     *        {@link android.view.Display#getDisplayId()}.
-     */
-    void onHbmEnabled(int displayId);
-
-    /**
-     * UdfpsController will call this method when the HBM is disabled.
-     *
-     * @param displayId The displayId for which the HBM is disabled. See
-     *        {@link android.view.Display#getDisplayId()}.
-     */
-    void onHbmDisabled(int displayId);
-}
-
diff --git a/core/jni/include_vm/android_runtime/vm.h b/core/java/android/hardware/fingerprint/IUdfpsOverlay.aidl
similarity index 60%
rename from core/jni/include_vm/android_runtime/vm.h
rename to core/java/android/hardware/fingerprint/IUdfpsOverlay.aidl
index a6e7c16..c99fccc 100644
--- a/core/jni/include_vm/android_runtime/vm.h
+++ b/core/java/android/hardware/fingerprint/IUdfpsOverlay.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,11 +14,16 @@
  * limitations under the License.
  */
 
-#pragma once
+package android.hardware.fingerprint;
 
-#include <jni.h>
+/**
+ * Interface for interacting with the under-display fingerprint sensor (UDFPS) overlay.
+ * @hide
+ */
+oneway interface IUdfpsOverlay {
+    // Shows the overlay.
+    void show(long requestId, int sensorId, int reason);
 
-// Get the Java VM. If the symbol doesn't exist at runtime, it means libandroid_runtime
-// is not loaded in the current process. If the symbol exists but it returns nullptr, it
-// means JavaVM is not yet started.
-extern "C" JavaVM* AndroidRuntimeGetJavaVM();
+    // Hides the overlay.
+    void hide(int sensorId);
+}
diff --git a/core/java/android/hardware/fingerprint/IUdfpsRefreshRateRequestCallback.aidl b/core/java/android/hardware/fingerprint/IUdfpsRefreshRateRequestCallback.aidl
new file mode 100644
index 0000000..8587348
--- /dev/null
+++ b/core/java/android/hardware/fingerprint/IUdfpsRefreshRateRequestCallback.aidl
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.hardware.fingerprint;
+
+/**
+ * A callback for UDFPS refresh rate. This allows other components to
+ * perform certain actions when the refresh rate is enabled or disabled.
+ * For example, a display manager implementation can subscribe to these
+ * events from UdfpsController when refresh rate is enabled or disabled.
+ *
+ * @hide
+ */
+oneway interface IUdfpsRefreshRateRequestCallback {
+    /**
+     * Sets the appropriate display refresh rate for UDFPS.
+     *
+     * @param displayId The displayId for which the refresh rate should be set. See
+     *        {@link android.view.Display#getDisplayId()}.
+     */
+    void onRequestEnabled(int displayId);
+
+    /**
+     * Unsets the appropriate display refresh rate for UDFPS.
+     *
+     * @param displayId The displayId for which the refresh rate should be unset. See
+     *        {@link android.view.Display#getDisplayId()}.
+     */
+    void onRequestDisabled(int displayId);
+}
+
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index f213224b..49c0f92 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -161,4 +161,11 @@
     void registerBatteryListener(int deviceId, IInputDeviceBatteryListener listener);
 
     void unregisterBatteryListener(int deviceId, IInputDeviceBatteryListener listener);
+
+    // Get the bluetooth address of an input device if known, returning null if it either is not
+    // connected via bluetooth or if the address cannot be determined.
+    @EnforcePermission("BLUETOOTH")
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+            + "android.Manifest.permission.BLUETOOTH)")
+    String getInputDeviceBluetoothAddress(int deviceId);
 }
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 8d4aac4..0cf15f7 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -1481,6 +1481,24 @@
     }
 
     /**
+     * Returns the Bluetooth address of this input device, if known.
+     *
+     * The returned string is always null if this input device is not connected
+     * via Bluetooth, or if the Bluetooth address of the device cannot be
+     * determined. The returned address will look like: "11:22:33:44:55:66".
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @Nullable
+    public String getInputDeviceBluetoothAddress(int deviceId) {
+        try {
+            return mIm.getInputDeviceBluetoothAddress(deviceId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Gets a vibrator service associated with an input device, always creates a new instance.
      * @return The vibrator, never null.
      * @hide
diff --git a/core/java/android/hardware/radio/ProgramSelector.java b/core/java/android/hardware/radio/ProgramSelector.java
index 36ac1a0..8a92135 100644
--- a/core/java/android/hardware/radio/ProgramSelector.java
+++ b/core/java/android/hardware/radio/ProgramSelector.java
@@ -533,7 +533,6 @@
         mProgramType = in.readInt();
         mPrimaryId = in.readTypedObject(Identifier.CREATOR);
         mSecondaryIds = in.createTypedArray(Identifier.CREATOR);
-        Arrays.sort(mSecondaryIds);
         if (Stream.of(mSecondaryIds).anyMatch(id -> id == null)) {
             throw new IllegalArgumentException("secondaryIds list must not contain nulls");
         }
diff --git a/core/java/android/hardware/usb/UsbDeviceConnection.java b/core/java/android/hardware/usb/UsbDeviceConnection.java
index 60d8cac..7c2e518 100644
--- a/core/java/android/hardware/usb/UsbDeviceConnection.java
+++ b/core/java/android/hardware/usb/UsbDeviceConnection.java
@@ -108,6 +108,34 @@
     }
 
     /**
+     * This is meant to be called by UsbRequest's queue() in order to synchronize on
+     * UsbDeviceConnection's mLock to prevent the connection being closed while queueing.
+     */
+    /* package */ boolean queueRequest(UsbRequest request, ByteBuffer buffer, int length) {
+        synchronized (mLock) {
+            if (!isOpen()) {
+                return false;
+            }
+
+            return request.queueIfConnectionOpen(buffer, length);
+        }
+    }
+
+    /**
+     * This is meant to be called by UsbRequest's queue() in order to synchronize on
+     * UsbDeviceConnection's mLock to prevent the connection being closed while queueing.
+     */
+    /* package */ boolean queueRequest(UsbRequest request, @Nullable ByteBuffer buffer) {
+        synchronized (mLock) {
+            if (!isOpen()) {
+                return false;
+            }
+
+            return request.queueIfConnectionOpen(buffer);
+        }
+    }
+
+    /**
      * Releases all system resources related to the device.
      * Once the object is closed it cannot be used again.
      * The client must call {@link UsbManager#openDevice} again
diff --git a/core/java/android/hardware/usb/UsbRequest.java b/core/java/android/hardware/usb/UsbRequest.java
index 6ac5e8d..beb0f8d 100644
--- a/core/java/android/hardware/usb/UsbRequest.java
+++ b/core/java/android/hardware/usb/UsbRequest.java
@@ -113,11 +113,13 @@
      * Releases all resources related to this request.
      */
     public void close() {
-        if (mNativeContext != 0) {
-            mEndpoint = null;
-            mConnection = null;
-            native_close();
-            mCloseGuard.close();
+        synchronized (mLock) {
+            if (mNativeContext != 0) {
+                mEndpoint = null;
+                mConnection = null;
+                native_close();
+                mCloseGuard.close();
+            }
         }
     }
 
@@ -191,10 +193,32 @@
      */
     @Deprecated
     public boolean queue(ByteBuffer buffer, int length) {
+        UsbDeviceConnection connection = mConnection;
+        if (connection == null) {
+            // The expected exception by CTS Verifier - USB Device test
+            throw new NullPointerException("invalid connection");
+        }
+
+        // Calling into the underlying UsbDeviceConnection to synchronize on its lock, to prevent
+        // the connection being closed while queueing.
+        return connection.queueRequest(this, buffer, length);
+    }
+
+    /**
+     * This is meant to be called from UsbDeviceConnection after synchronizing using the lock over
+     * there, to prevent the connection being closed while queueing.
+     */
+    /* package */ boolean queueIfConnectionOpen(ByteBuffer buffer, int length) {
+        UsbDeviceConnection connection = mConnection;
+        if (connection == null || !connection.isOpen()) {
+            // The expected exception by CTS Verifier - USB Device test
+            throw new NullPointerException("invalid connection");
+        }
+
         boolean out = (mEndpoint.getDirection() == UsbConstants.USB_DIR_OUT);
         boolean result;
 
-        if (mConnection.getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.P
+        if (connection.getContext().getApplicationInfo().targetSdkVersion < Build.VERSION_CODES.P
                 && length > MAX_USBFS_BUFFER_SIZE) {
             length = MAX_USBFS_BUFFER_SIZE;
         }
@@ -243,6 +267,28 @@
      * @return true if the queueing operation succeeded
      */
     public boolean queue(@Nullable ByteBuffer buffer) {
+        UsbDeviceConnection connection = mConnection;
+        if (connection == null) {
+            // The expected exception by CTS Verifier - USB Device test
+            throw new IllegalStateException("invalid connection");
+        }
+
+        // Calling into the underlying UsbDeviceConnection to synchronize on its lock, to prevent
+        // the connection being closed while queueing.
+        return connection.queueRequest(this, buffer);
+    }
+
+    /**
+     * This is meant to be called from UsbDeviceConnection after synchronizing using the lock over
+     * there, to prevent the connection being closed while queueing.
+     */
+    /* package */ boolean queueIfConnectionOpen(@Nullable ByteBuffer buffer) {
+        UsbDeviceConnection connection = mConnection;
+        if (connection == null || !connection.isOpen()) {
+            // The expected exception by CTS Verifier - USB Device test
+            throw new IllegalStateException("invalid connection");
+        }
+
         // Request need to be initialized
         Preconditions.checkState(mNativeContext != 0, "request is not initialized");
 
@@ -260,7 +306,7 @@
                 mIsUsingNewQueue = true;
                 wasQueued = native_queue(null, 0, 0);
             } else {
-                if (mConnection.getContext().getApplicationInfo().targetSdkVersion
+                if (connection.getContext().getApplicationInfo().targetSdkVersion
                         < Build.VERSION_CODES.P) {
                     // Can only send/receive MAX_USBFS_BUFFER_SIZE bytes at once
                     Preconditions.checkArgumentInRange(buffer.remaining(), 0, MAX_USBFS_BUFFER_SIZE,
@@ -363,11 +409,12 @@
      * @return true if cancelling succeeded
      */
     public boolean cancel() {
-        if (mConnection == null) {
+        UsbDeviceConnection connection = mConnection;
+        if (connection == null) {
             return false;
         }
 
-        return mConnection.cancelRequest(this);
+        return connection.cancelRequest(this);
     }
 
     /**
@@ -382,7 +429,8 @@
      * @return true if cancelling succeeded.
      */
     /* package */ boolean cancelIfOpen() {
-        if (mNativeContext == 0 || (mConnection != null && !mConnection.isOpen())) {
+        UsbDeviceConnection connection = mConnection;
+        if (mNativeContext == 0 || (connection != null && !connection.isOpen())) {
             Log.w(TAG,
                     "Detected attempt to cancel a request on a connection which isn't open");
             return false;
diff --git a/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java b/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java
index 4f09bee..95aecfe 100644
--- a/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java
+++ b/core/java/android/inputmethodservice/IRemoteInputConnectionInvoker.java
@@ -16,30 +16,29 @@
 
 package android.inputmethodservice;
 
+import static android.view.inputmethod.TextBoundsInfoResult.CODE_CANCELLED;
+
 import android.annotation.AnyThread;
 import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.graphics.RectF;
 import android.os.Bundle;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.view.KeyEvent;
 import android.view.inputmethod.CompletionInfo;
 import android.view.inputmethod.CorrectionInfo;
-import android.view.inputmethod.DeleteGesture;
-import android.view.inputmethod.DeleteRangeGesture;
 import android.view.inputmethod.ExtractedText;
 import android.view.inputmethod.ExtractedTextRequest;
 import android.view.inputmethod.HandwritingGesture;
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputContentInfo;
-import android.view.inputmethod.InsertGesture;
-import android.view.inputmethod.JoinOrSplitGesture;
-import android.view.inputmethod.RemoveSpaceGesture;
-import android.view.inputmethod.SelectGesture;
-import android.view.inputmethod.SelectRangeGesture;
+import android.view.inputmethod.ParcelableHandwritingGesture;
 import android.view.inputmethod.SurroundingText;
 import android.view.inputmethod.TextAttribute;
+import android.view.inputmethod.TextBoundsInfo;
+import android.view.inputmethod.TextBoundsInfoResult;
 
 import com.android.internal.infra.AndroidFuture;
 import com.android.internal.inputmethod.IRemoteInputConnection;
@@ -47,6 +46,7 @@
 
 import java.util.Objects;
 import java.util.concurrent.Executor;
+import java.util.function.Consumer;
 import java.util.function.IntConsumer;
 
 /**
@@ -98,6 +98,44 @@
     };
 
     /**
+     * Subclass of {@link ResultReceiver} used by
+     * {@link #requestTextBoundsInfo(RectF, Executor, Consumer)} for providing
+     * callback.
+     */
+    private static final class TextBoundsInfoResultReceiver extends ResultReceiver {
+        @Nullable
+        private Consumer<TextBoundsInfoResult> mConsumer;
+        @Nullable
+        private Executor mExecutor;
+
+        TextBoundsInfoResultReceiver(@NonNull Executor executor,
+                @NonNull Consumer<TextBoundsInfoResult> consumer) {
+            super(null);
+            mExecutor = executor;
+            mConsumer = consumer;
+        }
+
+        @Override
+        protected void onReceiveResult(@TextBoundsInfoResult.ResultCode int resultCode,
+                @Nullable Bundle resultData) {
+            synchronized (this) {
+                if (mExecutor != null && mConsumer != null) {
+                    final TextBoundsInfoResult textBoundsInfoResult = new TextBoundsInfoResult(
+                            resultCode, TextBoundsInfo.createFromBundle(resultData));
+                    mExecutor.execute(() -> mConsumer.accept(textBoundsInfoResult));
+                    // provide callback only once.
+                    clear();
+                }
+            }
+        }
+
+        private void clear() {
+            mExecutor = null;
+            mConsumer = null;
+        }
+    }
+
+    /**
      * Creates a new instance of {@link IRemoteInputConnectionInvoker} for the given
      * {@link IRemoteInputConnection}.
      *
@@ -637,50 +675,19 @@
     }
 
     /**
-     * Invokes one of {@link IRemoteInputConnection#performHandwritingSelectGesture},
-     * {@link IRemoteInputConnection#performHandwritingSelectRangeGesture},
-     * {@link IRemoteInputConnection#performHandwritingDeleteGesture},
-     * {@link IRemoteInputConnection#performHandwritingDeleteRangeGesture},
-     * {@link IRemoteInputConnection#performHandwritingInsertGesture},
-     * {@link IRemoteInputConnection#performHandwritingRemoveSpaceGesture},
-     * {@link IRemoteInputConnection#performHandwritingJoinOrSplitGesture}.
+     * Invokes {@link IRemoteInputConnection#performHandwritingGesture(
+     * InputConnectionCommandHeader, ParcelableHandwritingGesture, ResultReceiver)}.
      */
     @AnyThread
-    public void performHandwritingGesture(
-            @NonNull HandwritingGesture gesture, @Nullable @CallbackExecutor Executor executor,
-            @Nullable IntConsumer consumer) {
-
+    public void performHandwritingGesture(@NonNull ParcelableHandwritingGesture gesture,
+            @Nullable @CallbackExecutor Executor executor, @Nullable IntConsumer consumer) {
         ResultReceiver resultReceiver = null;
         if (consumer != null) {
             Objects.requireNonNull(executor);
             resultReceiver = new IntResultReceiver(executor, consumer);
         }
         try {
-            if (gesture instanceof SelectGesture) {
-                mConnection.performHandwritingSelectGesture(
-                        createHeader(), (SelectGesture) gesture, resultReceiver);
-            } else if (gesture instanceof SelectRangeGesture) {
-                mConnection.performHandwritingSelectRangeGesture(
-                        createHeader(), (SelectRangeGesture) gesture, resultReceiver);
-            } else if (gesture instanceof InsertGesture) {
-                mConnection.performHandwritingInsertGesture(
-                        createHeader(), (InsertGesture) gesture, resultReceiver);
-            } else if (gesture instanceof DeleteGesture) {
-                mConnection.performHandwritingDeleteGesture(
-                        createHeader(), (DeleteGesture) gesture, resultReceiver);
-            } else if (gesture instanceof DeleteRangeGesture) {
-                mConnection.performHandwritingDeleteRangeGesture(
-                        createHeader(), (DeleteRangeGesture) gesture, resultReceiver);
-            } else if (gesture instanceof RemoveSpaceGesture) {
-                mConnection.performHandwritingRemoveSpaceGesture(
-                        createHeader(), (RemoveSpaceGesture) gesture, resultReceiver);
-            } else if (gesture instanceof JoinOrSplitGesture) {
-                mConnection.performHandwritingJoinOrSplitGesture(
-                        createHeader(), (JoinOrSplitGesture) gesture, resultReceiver);
-            } else if (consumer != null && executor != null) {
-                executor.execute(()
-                        -> consumer.accept(InputConnection.HANDWRITING_GESTURE_RESULT_UNSUPPORTED));
-            }
+            mConnection.performHandwritingGesture(createHeader(), gesture, resultReceiver);
         } catch (RemoteException e) {
             if (consumer != null && executor != null) {
                 executor.execute(() -> consumer.accept(
@@ -736,6 +743,28 @@
     }
 
     /**
+     * Invokes {@link IRemoteInputConnection#requestTextBoundsInfo(InputConnectionCommandHeader,
+     * RectF, ResultReceiver)}
+     * @param rectF {@code rectF} parameter to be passed.
+     * @param executor {@code Executor} parameter to be passed.
+     * @param consumer {@code Consumer} parameter to be passed.
+     */
+    @AnyThread
+    public void requestTextBoundsInfo(
+            @NonNull RectF rectF, @NonNull @CallbackExecutor Executor executor,
+            @NonNull Consumer<TextBoundsInfoResult> consumer) {
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(consumer);
+
+        final ResultReceiver resultReceiver = new TextBoundsInfoResultReceiver(executor, consumer);
+        try {
+            mConnection.requestTextBoundsInfo(createHeader(), rectF, resultReceiver);
+        } catch (RemoteException e) {
+            executor.execute(() -> consumer.accept(new TextBoundsInfoResult(CODE_CANCELLED)));
+        }
+    }
+
+    /**
      * Invokes {@link IRemoteInputConnection#commitContent(InputConnectionCommandHeader,
      * InputContentInfo, int, Bundle, AndroidFuture)}.
      *
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 39d362b..cea46f3 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -1774,16 +1774,13 @@
     }
 
     /**
-     * Implement to return our standard {@link InputMethodImpl}.  Subclasses
-     * can override to provide their own customized version.
+     * Implement to return our standard {@link InputMethodImpl}.
      *
-     * @deprecated IME developers don't need to override this method to get callbacks information.
-     * Most methods in {@link InputMethodImpl} have corresponding callbacks.
-     * Use {@link InputMethodService#onBindInput()}, {@link InputMethodService#onUnbindInput()},
-     * {@link InputMethodService#onWindowShown()}, {@link InputMethodService#onWindowHidden()}, etc.
-     *
-     * <p>Starting from Android U and later, override this method won't guarantee that IME works
-     * as previous platform behavior.</p>
+     * @deprecated Overriding or calling this method is strongly discouraged. A future version of
+     * Android will remove the ability to use this method. Use the callbacks on
+     * {@link InputMethodService} as {@link InputMethodService#onBindInput()},
+     * {@link InputMethodService#onUnbindInput()}, {@link InputMethodService#onWindowShown()},
+     * {@link InputMethodService#onWindowHidden()}, etc.
      */
     @Deprecated
     @Override
@@ -1792,18 +1789,17 @@
     }
     
     /**
-     * Implement to return our standard {@link InputMethodSessionImpl}.  Subclasses
-     * can override to provide their own customized version.
+     * Implement to return our standard {@link InputMethodSessionImpl}.
      *
-     * @deprecated IME developers don't need to override this method to get callbacks information.
+     * <p>IMEs targeting on Android U and above cannot override this method, or an
+     * {@link LinkageError} would be thrown.</p>
+     *
+     * @deprecated Overriding or calling this method is strongly discouraged.
      * Most methods in {@link InputMethodSessionImpl} have corresponding callbacks.
      * Use {@link InputMethodService#onFinishInput()},
      * {@link InputMethodService#onDisplayCompletions(CompletionInfo[])},
      * {@link InputMethodService#onUpdateExtractedText(int, ExtractedText)},
      * {@link InputMethodService#onUpdateSelection(int, int, int, int, int, int)} instead.
-     *
-     * <p>IMEs targeting on Android U and above cannot override this method, or an
-     * {@link LinkageError} would be thrown.</p>
      */
     @Deprecated
     @Override
diff --git a/core/java/android/inputmethodservice/RemoteInputConnection.java b/core/java/android/inputmethodservice/RemoteInputConnection.java
index 09e86c4..976e71f 100644
--- a/core/java/android/inputmethodservice/RemoteInputConnection.java
+++ b/core/java/android/inputmethodservice/RemoteInputConnection.java
@@ -21,6 +21,7 @@
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.graphics.RectF;
 import android.os.Bundle;
 import android.os.Handler;
 import android.util.Log;
@@ -32,8 +33,10 @@
 import android.view.inputmethod.HandwritingGesture;
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputContentInfo;
+import android.view.inputmethod.ParcelableHandwritingGesture;
 import android.view.inputmethod.SurroundingText;
 import android.view.inputmethod.TextAttribute;
+import android.view.inputmethod.TextBoundsInfoResult;
 
 import com.android.internal.inputmethod.CancellationGroup;
 import com.android.internal.inputmethod.CompletableFutureUtil;
@@ -44,6 +47,7 @@
 import java.lang.ref.WeakReference;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.Executor;
+import java.util.function.Consumer;
 import java.util.function.IntConsumer;
 
 /**
@@ -418,7 +422,8 @@
     public void performHandwritingGesture(
             @NonNull HandwritingGesture gesture, @Nullable @CallbackExecutor Executor executor,
             @Nullable IntConsumer consumer) {
-        mInvoker.performHandwritingGesture(gesture, executor, consumer);
+        mInvoker.performHandwritingGesture(ParcelableHandwritingGesture.of(gesture), executor,
+                consumer);
     }
 
     @AnyThread
@@ -460,6 +465,13 @@
     }
 
     @AnyThread
+    public void requestTextBoundsInfo(
+            @NonNull RectF rectF, @NonNull @CallbackExecutor Executor executor,
+            @NonNull Consumer<TextBoundsInfoResult> consumer) {
+        mInvoker.requestTextBoundsInfo(rectF, executor, consumer);
+    }
+
+    @AnyThread
     public Handler getHandler() {
         // Nothing should happen when called from input method.
         return null;
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index 2643558..3c4abab 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -1219,25 +1219,40 @@
     @UnsupportedAppUsage
     private boolean execTransact(int code, long dataObj, long replyObj,
             int flags) {
+
+        Parcel data = Parcel.obtain(dataObj);
+        Parcel reply = Parcel.obtain(replyObj);
+
         // At that point, the parcel request headers haven't been parsed so we do not know what
         // {@link WorkSource} the caller has set. Use calling UID as the default.
-        final int callingUid = Binder.getCallingUid();
-        final long origWorkSource = ThreadLocalWorkSource.setUid(callingUid);
+        //
+        // TODO: this is wrong - we should attribute along the entire call route
+        // also this attribution logic should move to native code - it only works
+        // for Java now
+        //
+        // This attribution support is not generic and therefore not support in RPC mode
+        final int callingUid = data.isForRpc() ? -1 : Binder.getCallingUid();
+        final long origWorkSource = callingUid == -1
+                ? -1 : ThreadLocalWorkSource.setUid(callingUid);
+
         try {
-            return execTransactInternal(code, dataObj, replyObj, flags, callingUid);
+            return execTransactInternal(code, data, reply, flags, callingUid);
         } finally {
-            ThreadLocalWorkSource.restore(origWorkSource);
+            reply.recycle();
+            data.recycle();
+
+            if (callingUid != -1) {
+                ThreadLocalWorkSource.restore(origWorkSource);
+            }
         }
     }
 
-    private boolean execTransactInternal(int code, long dataObj, long replyObj, int flags,
+    private boolean execTransactInternal(int code, Parcel data, Parcel reply, int flags,
             int callingUid) {
         // Make sure the observer won't change while processing a transaction.
         final BinderInternal.Observer observer = sObserver;
         final CallSession callSession =
                 observer != null ? observer.callStarted(this, code, UNSET_WORKSOURCE) : null;
-        Parcel data = Parcel.obtain(dataObj);
-        Parcel reply = Parcel.obtain(replyObj);
         // Theoretically, we should call transact, which will call onTransact,
         // but all that does is rewind it, and we just got these from an IPC,
         // so we'll just call it directly.
@@ -1268,8 +1283,10 @@
 
         final boolean tracingEnabled = tagEnabled && transactionTraceName != null;
         try {
+            // TODO - this logic should not be in Java - it should be in native
+            // code in libbinder so that it works for all binder users.
             final BinderCallHeavyHitterWatcher heavyHitterWatcher = sHeavyHitterWatcher;
-            if (heavyHitterWatcher != null) {
+            if (heavyHitterWatcher != null && callingUid != -1) {
                 // Notify the heavy hitter watcher, if it's enabled.
                 heavyHitterWatcher.onTransaction(callingUid, getClass(), code);
             }
@@ -1277,7 +1294,10 @@
                 Trace.traceBegin(Trace.TRACE_TAG_AIDL, transactionTraceName);
             }
 
-            if ((flags & FLAG_COLLECT_NOTED_APP_OPS) != 0) {
+            // TODO - this logic should not be in Java - it should be in native
+            // code in libbinder so that it works for all binder users. Further,
+            // this should not re-use flags.
+            if ((flags & FLAG_COLLECT_NOTED_APP_OPS) != 0 && callingUid != -1) {
                 AppOpsManager.startNotedAppOpsCollection(callingUid);
                 try {
                     res = onTransact(code, data, reply, flags);
@@ -1320,8 +1340,6 @@
             }
 
             checkParcel(this, code, reply, "Unreasonably large binder reply buffer");
-            reply.recycle();
-            data.recycle();
         }
 
         // Just in case -- we are done with the IPC, so there should be no more strict
diff --git a/core/java/android/os/IUserManager.aidl b/core/java/android/os/IUserManager.aidl
index 933769a..8eaa5ad 100644
--- a/core/java/android/os/IUserManager.aidl
+++ b/core/java/android/os/IUserManager.aidl
@@ -69,6 +69,7 @@
     boolean canAddMoreManagedProfiles(int userId, boolean allowedToRemoveOne);
     UserInfo getProfileParent(int userId);
     boolean isSameProfileGroup(int userId, int otherUserHandle);
+    boolean isHeadlessSystemUserMode();
     boolean isUserOfType(int userId, in String userType);
     @UnsupportedAppUsage
     UserInfo getUserInfo(int userId);
diff --git a/core/java/android/os/OWNERS b/core/java/android/os/OWNERS
index d84037f..9d8df7e 100644
--- a/core/java/android/os/OWNERS
+++ b/core/java/android/os/OWNERS
@@ -70,3 +70,7 @@
 
 # Tracing
 per-file Trace.java = file:/TRACE_OWNERS
+
+# PermissionEnforcer
+per-file PermissionEnforcer.java = tweek@google.com, brufino@google.com
+per-file PermissionEnforcer.java = file:/core/java/android/permission/OWNERS
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index d451765..1673ade 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -367,6 +367,8 @@
     @FastNative
     private static native void nativeMarkForBinder(long nativePtr, IBinder binder);
     @CriticalNative
+    private static native boolean nativeIsForRpc(long nativePtr);
+    @CriticalNative
     private static native int nativeDataSize(long nativePtr);
     @CriticalNative
     private static native int nativeDataAvail(long nativePtr);
@@ -559,9 +561,11 @@
      */
     public final void recycle() {
         if (mRecycled) {
-            Log.w(TAG, "Recycle called on unowned Parcel. (recycle twice?) Here: "
+            Log.wtf(TAG, "Recycle called on unowned Parcel. (recycle twice?) Here: "
                     + Log.getStackTraceString(new Throwable())
                     + " Original recycle call (if DEBUG_RECYCLE): ", mStack);
+
+            return;
         }
         mRecycled = true;
 
@@ -644,6 +648,15 @@
         nativeMarkForBinder(mNativePtr, binder);
     }
 
+    /**
+     * Whether this Parcel is written for an RPC transaction.
+     *
+     * @hide
+     */
+    public final boolean isForRpc() {
+        return nativeIsForRpc(mNativePtr);
+    }
+
     /** @hide */
     @ParcelFlags
     @TestApi
diff --git a/core/java/android/os/Parcelable.java b/core/java/android/os/Parcelable.java
index 8a80457..a2b0486 100644
--- a/core/java/android/os/Parcelable.java
+++ b/core/java/android/os/Parcelable.java
@@ -188,7 +188,7 @@
      * @return true if this parcelable is stable.
      * @hide
      */
-    @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
+    @SystemApi(client = SystemApi.Client.PRIVILEGED_APPS)
     default @Stability int getStability() {
         return PARCELABLE_STABILITY_LOCAL;
     }
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 51dc643..3d20d63 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -91,7 +91,6 @@
 public class UserManager {
 
     private static final String TAG = "UserManager";
-    private static final boolean VERBOSE = false;
 
     @UnsupportedAppUsage
     private final IUserManager mService;
@@ -104,6 +103,9 @@
     /** The userType of UserHandle.myUserId(); empty string if not a profile; null until cached. */
     private String mProfileTypeOfProcessUser = null;
 
+    /** Whether the device is in headless system user mode; null until cached. */
+    private static Boolean sIsHeadlessSystemUser = null;
+
     /**
      * User type representing a {@link UserHandle#USER_SYSTEM system} user that is a human user.
      * This type of user cannot be created; it can only pre-exist on first boot.
@@ -1488,6 +1490,22 @@
     public static final String KEY_RESTRICTIONS_PENDING = "restrictions_pending";
 
     /**
+     * Specifies if a user is not allowed to use 2g networks.
+     *
+     * <p>This restriction can only be set by a device owner or a profile owner of an
+     * organization-owned managed profile on the parent profile.
+     * In all cases, the setting applies globally on the device and will prevent the device from
+     * scanning for or connecting to 2g networks, except in the case of an emergency.
+     *
+     * <p>The default value is <code>false</code>.
+     *
+     * @see DevicePolicyManager#addUserRestriction(ComponentName, String)
+     * @see DevicePolicyManager#clearUserRestriction(ComponentName, String)
+     * @see #getUserRestrictions()
+     */
+    public static final String DISALLOW_CELLULAR_2G = "no_cellular_2g";
+
+    /**
      * List of key values that can be passed into the various user restriction related methods
      * in {@link UserManager} & {@link DevicePolicyManager}.
      * Note: This is slightly different from the real set of user restrictions listed in {@link
@@ -1568,6 +1586,7 @@
             DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI,
             DISALLOW_WIFI_DIRECT,
             DISALLOW_ADD_WIFI_CONFIG,
+            DISALLOW_CELLULAR_2G,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface UserRestrictionKey {}
@@ -2051,28 +2070,20 @@
      * @return whether the device is running in a headless system user mode.
      */
     public static boolean isHeadlessSystemUserMode() {
-        final boolean realMode = RoSystemProperties.MULTIUSER_HEADLESS_SYSTEM_USER;
-        if (!Build.isDebuggable()) {
-            return realMode;
+        // No need for synchronization.  Once it becomes non-null, it'll be non-null forever.
+        // (Its value is determined when UMS is constructed and cannot change.)
+        // Worst case we might end up calling the AIDL method multiple times but that's fine.
+        if (sIsHeadlessSystemUser == null) {
+            // Unfortunately this API is static, but the property no longer is. So go fetch the UMS.
+            try {
+                final IUserManager service = IUserManager.Stub.asInterface(
+                        ServiceManager.getService(Context.USER_SERVICE));
+                sIsHeadlessSystemUser = service.isHeadlessSystemUserMode();
+            } catch (RemoteException re) {
+                throw re.rethrowFromSystemServer();
+            }
         }
-
-        final String emulatedMode = SystemProperties.get(SYSTEM_USER_MODE_EMULATION_PROPERTY);
-        switch (emulatedMode) {
-            case SYSTEM_USER_MODE_EMULATION_FULL:
-                if (VERBOSE) Log.v(TAG, "isHeadlessSystemUserMode(): emulating as false");
-                return false;
-            case SYSTEM_USER_MODE_EMULATION_HEADLESS:
-                if (VERBOSE) Log.v(TAG, "isHeadlessSystemUserMode(): emulating as true");
-                return true;
-            case SYSTEM_USER_MODE_EMULATION_DEFAULT:
-            case "": // property not set
-                return realMode;
-            default:
-                Log.wtf(TAG, "isHeadlessSystemUserMode(): invalid value of property "
-                        + SYSTEM_USER_MODE_EMULATION_PROPERTY + " (" + emulatedMode + "); using"
-                                + " default value (headless=" + realMode + ")");
-                return realMode;
-        }
+        return sIsHeadlessSystemUser;
     }
 
     /**
diff --git a/core/java/android/preference/SeekBarVolumizer.java b/core/java/android/preference/SeekBarVolumizer.java
index 3bf9ca0..b117a9a 100644
--- a/core/java/android/preference/SeekBarVolumizer.java
+++ b/core/java/android/preference/SeekBarVolumizer.java
@@ -16,7 +16,9 @@
 
 package android.preference;
 
+import android.Manifest;
 import android.annotation.NonNull;
+import android.annotation.RequiresPermission;
 import android.app.NotificationManager;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.BroadcastReceiver;
@@ -35,6 +37,7 @@
 import android.os.HandlerThread;
 import android.os.Message;
 import android.preference.VolumePreference.VolumeStore;
+import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.provider.Settings.Global;
 import android.provider.Settings.System;
@@ -44,6 +47,7 @@
 import android.widget.SeekBar.OnSeekBarChangeListener;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.internal.os.SomeArgs;
 
 import java.util.concurrent.TimeUnit;
@@ -115,7 +119,6 @@
     private final int mMaxStreamVolume;
     private boolean mAffectedByRingerMode;
     private boolean mNotificationOrRing;
-    private final boolean mNotifAliasRing;
     private final Receiver mReceiver = new Receiver();
 
     private Handler mHandler;
@@ -158,6 +161,7 @@
         this(context, streamType, defaultUri, callback, true /* playSample */);
     }
 
+    @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
     public SeekBarVolumizer(
             Context context,
             int streamType,
@@ -180,8 +184,6 @@
         if (mNotificationOrRing) {
             mRingerMode = mAudioManager.getRingerModeInternal();
         }
-        mNotifAliasRing = mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_alias_ring_notif_stream_types);
         mZenMode = mNotificationManager.getZenMode();
 
         if (hasAudioProductStrategies()) {
@@ -288,7 +290,9 @@
              * so that when user attempts to slide the notification seekbar out of vibrate the
              * seekbar doesn't wrongly snap back to 0 when the streams aren't aliased
              */
-            if (mNotifAliasRing || mStreamType == AudioManager.STREAM_RING
+            if (!DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+                    SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false)
+                    || mStreamType == AudioManager.STREAM_RING
                     || (mStreamType == AudioManager.STREAM_NOTIFICATION && mMuted)) {
                 mSeekBar.setProgress(0, true);
             }
@@ -365,7 +369,9 @@
         // set the time of stop volume
         if ((mStreamType == AudioManager.STREAM_VOICE_CALL
                 || mStreamType == AudioManager.STREAM_RING
-                || (!mNotifAliasRing && mStreamType == AudioManager.STREAM_NOTIFICATION)
+                || (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false)
+                && mStreamType == AudioManager.STREAM_NOTIFICATION)
                 || mStreamType == AudioManager.STREAM_ALARM)) {
             sStopVolumeTime = java.lang.System.currentTimeMillis();
         }
@@ -644,8 +650,10 @@
         }
 
         private void updateVolumeSlider(int streamType, int streamValue) {
-            final boolean streamMatch = mNotifAliasRing && mNotificationOrRing
-                    ? isNotificationOrRing(streamType) : streamType == mStreamType;
+            final boolean streamMatch =  !DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+                    SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false)
+                    && mNotificationOrRing ? isNotificationOrRing(streamType) :
+                    streamType == mStreamType;
             if (mSeekBar != null && streamMatch && streamValue != -1) {
                 final boolean muted = mAudioManager.isStreamMute(mStreamType)
                         || streamValue == 0;
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 4502eec..fab6f7b 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -3374,9 +3374,26 @@
                     }
                 }
 
-                // Fetch all flags for the namespace at once for caching purposes
-                Bundle b = cp.call(cr.getAttributionSource(),
-                        mProviderHolder.mUri.getAuthority(), mCallListCommand, null, args);
+                Bundle b;
+                // b/252663068: if we're in system server and the caller did not call
+                // clearCallingIdentity, the read would fail due to mismatched AttributionSources.
+                // TODO(b/256013480): remove this bypass after fixing the callers in system server.
+                if (namespace.equals(DeviceConfig.NAMESPACE_DEVICE_POLICY_MANAGER)
+                        && Settings.isInSystemServer()
+                        && Binder.getCallingUid() != Process.myUid()) {
+                    final long token = Binder.clearCallingIdentity();
+                    try {
+                        // Fetch all flags for the namespace at once for caching purposes
+                        b = cp.call(cr.getAttributionSource(),
+                                mProviderHolder.mUri.getAuthority(), mCallListCommand, null, args);
+                    } finally {
+                        Binder.restoreCallingIdentity(token);
+                    }
+                } else {
+                    // Fetch all flags for the namespace at once for caching purposes
+                    b = cp.call(cr.getAttributionSource(),
+                            mProviderHolder.mUri.getAuthority(), mCallListCommand, null, args);
+                }
                 if (b == null) {
                     // Invalid response, return an empty map
                     return keyValues;
@@ -9859,6 +9876,13 @@
                 "fingerprint_side_fps_auth_downtime";
 
         /**
+         * Whether or not a SFPS device is required to be interactive for auth to unlock the device.
+         * @hide
+         */
+        public static final String SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED =
+                "sfps_require_screen_on_to_auth_enabled";
+
+        /**
          * Whether or not debugging is enabled.
          * @hide
          */
diff --git a/core/java/android/service/autofill/FillRequest.java b/core/java/android/service/autofill/FillRequest.java
index b4010a4..0f7c9b6 100644
--- a/core/java/android/service/autofill/FillRequest.java
+++ b/core/java/android/service/autofill/FillRequest.java
@@ -111,6 +111,12 @@
      */
     public static final @RequestFlags int FLAG_IME_SHOWING = 0x80;
 
+    /**
+     * Indicates whether autofill session should reset the fill dialog state.
+     * @hide
+     */
+    public static final @RequestFlags int FLAG_RESET_FILL_DIALOG_STATE = 0x100;
+
     /** @hide */
     public static final int INVALID_REQUEST_ID = Integer.MIN_VALUE;
 
@@ -208,7 +214,8 @@
         FLAG_PASSWORD_INPUT_TYPE,
         FLAG_VIEW_NOT_FOCUSED,
         FLAG_SUPPORTS_FILL_DIALOG,
-        FLAG_IME_SHOWING
+        FLAG_IME_SHOWING,
+        FLAG_RESET_FILL_DIALOG_STATE
     })
     @Retention(RetentionPolicy.SOURCE)
     @DataClass.Generated.Member
@@ -236,6 +243,8 @@
                     return "FLAG_SUPPORTS_FILL_DIALOG";
             case FLAG_IME_SHOWING:
                     return "FLAG_IME_SHOWING";
+            case FLAG_RESET_FILL_DIALOG_STATE:
+                    return "FLAG_RESET_FILL_DIALOG_STATE";
             default: return Integer.toHexString(value);
         }
     }
@@ -312,7 +321,8 @@
                         | FLAG_PASSWORD_INPUT_TYPE
                         | FLAG_VIEW_NOT_FOCUSED
                         | FLAG_SUPPORTS_FILL_DIALOG
-                        | FLAG_IME_SHOWING);
+                        | FLAG_IME_SHOWING
+                        | FLAG_RESET_FILL_DIALOG_STATE);
         this.mInlineSuggestionsRequest = inlineSuggestionsRequest;
         this.mDelayedFillIntentSender = delayedFillIntentSender;
 
@@ -473,7 +483,8 @@
                         | FLAG_PASSWORD_INPUT_TYPE
                         | FLAG_VIEW_NOT_FOCUSED
                         | FLAG_SUPPORTS_FILL_DIALOG
-                        | FLAG_IME_SHOWING);
+                        | FLAG_IME_SHOWING
+                        | FLAG_RESET_FILL_DIALOG_STATE);
         this.mInlineSuggestionsRequest = inlineSuggestionsRequest;
         this.mDelayedFillIntentSender = delayedFillIntentSender;
 
@@ -495,10 +506,10 @@
     };
 
     @DataClass.Generated(
-            time = 1647856966565L,
+            time = 1663290803064L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/service/autofill/FillRequest.java",
-            inputSignatures = "public static final @android.service.autofill.FillRequest.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_PASSWORD_INPUT_TYPE\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_VIEW_NOT_FOCUSED\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_SUPPORTS_FILL_DIALOG\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_IME_SHOWING\npublic static final  int INVALID_REQUEST_ID\nprivate final  int mId\nprivate final @android.annotation.NonNull java.util.List<android.service.autofill.FillContext> mFillContexts\nprivate final @android.annotation.Nullable android.os.Bundle mClientState\nprivate final @android.service.autofill.FillRequest.RequestFlags int mFlags\nprivate final @android.annotation.Nullable android.view.inputmethod.InlineSuggestionsRequest mInlineSuggestionsRequest\nprivate final @android.annotation.Nullable android.content.IntentSender mDelayedFillIntentSender\nprivate  void onConstructed()\nclass FillRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
+            inputSignatures = "public static final @android.service.autofill.FillRequest.RequestFlags int FLAG_MANUAL_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_COMPATIBILITY_MODE_REQUEST\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_PASSWORD_INPUT_TYPE\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_VIEW_NOT_FOCUSED\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_SUPPORTS_FILL_DIALOG\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_IME_SHOWING\npublic static final @android.service.autofill.FillRequest.RequestFlags int FLAG_RESET_FILL_DIALOG_STATE\npublic static final  int INVALID_REQUEST_ID\nprivate final  int mId\nprivate final @android.annotation.NonNull java.util.List<android.service.autofill.FillContext> mFillContexts\nprivate final @android.annotation.Nullable android.os.Bundle mClientState\nprivate final @android.service.autofill.FillRequest.RequestFlags int mFlags\nprivate final @android.annotation.Nullable android.view.inputmethod.InlineSuggestionsRequest mInlineSuggestionsRequest\nprivate final @android.annotation.Nullable android.content.IntentSender mDelayedFillIntentSender\nprivate  void onConstructed()\nclass FillRequest extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genToString=true, genHiddenConstructor=true, genHiddenConstDefs=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/service/credentials/CreateCredentialResponse.java b/core/java/android/service/credentials/CreateCredentialResponse.java
index 559b1ca..e330d1e 100644
--- a/core/java/android/service/credentials/CreateCredentialResponse.java
+++ b/core/java/android/service/credentials/CreateCredentialResponse.java
@@ -35,18 +35,22 @@
 public final class CreateCredentialResponse implements Parcelable {
     private final @Nullable CharSequence mHeader;
     private final @NonNull List<SaveEntry> mSaveEntries;
+    private final @Nullable Action mRemoteSaveEntry;
+    //TODO : Add actions if needed
 
     private CreateCredentialResponse(@NonNull Parcel in) {
         mHeader = in.readCharSequence();
         List<SaveEntry> saveEntries = new ArrayList<>();
         in.readTypedList(saveEntries, SaveEntry.CREATOR);
         mSaveEntries = saveEntries;
+        mRemoteSaveEntry = in.readTypedObject(Action.CREATOR);
     }
 
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeCharSequence(mHeader);
         dest.writeTypedList(mSaveEntries);
+        dest.writeTypedObject(mRemoteSaveEntry, flags);
     }
 
     @Override
@@ -69,11 +73,13 @@
 
     /* package-private */ CreateCredentialResponse(
             @Nullable CharSequence header,
-            @NonNull List<SaveEntry> saveEntries) {
+            @NonNull List<SaveEntry> saveEntries,
+            @Nullable Action remoteSaveEntry) {
         this.mHeader = header;
         this.mSaveEntries = saveEntries;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, mSaveEntries);
+        this.mRemoteSaveEntry = remoteSaveEntry;
     }
 
     /** Returns the header to be displayed on the UI. */
@@ -86,6 +92,11 @@
         return mSaveEntries;
     }
 
+    /** Returns the remote save entry to be displayed on the UI. */
+    public @NonNull Action getRemoteSaveEntry() {
+        return mRemoteSaveEntry;
+    }
+
     /**
      * A builder for {@link CreateCredentialResponse}
      */
@@ -94,6 +105,7 @@
 
         private @Nullable CharSequence mHeader;
         private @NonNull List<SaveEntry> mSaveEntries = new ArrayList<>();
+        private @Nullable Action mRemoteSaveEntry;
 
         /** Sets the header to be displayed on the UI. */
         public @NonNull Builder setHeader(@Nullable CharSequence header) {
@@ -126,6 +138,14 @@
         }
 
         /**
+         * Sets a remote save entry to be shown on the UI.
+         */
+        public @NonNull Builder setRemoteSaveEntry(@Nullable Action remoteSaveEntry) {
+            mRemoteSaveEntry = remoteSaveEntry;
+            return this;
+        }
+
+        /**
          * Builds the instance.
          *
          * @throws IllegalArgumentException If {@code saveEntries} is empty.
@@ -135,7 +155,8 @@
                     + "not be empty");
             return new CreateCredentialResponse(
                     mHeader,
-                    mSaveEntries);
+                    mSaveEntries,
+                    mRemoteSaveEntry);
         }
     }
 }
diff --git a/core/java/android/service/credentials/CredentialEntry.java b/core/java/android/service/credentials/CredentialEntry.java
index 4cc43a1..1d4ac25 100644
--- a/core/java/android/service/credentials/CredentialEntry.java
+++ b/core/java/android/service/credentials/CredentialEntry.java
@@ -140,8 +140,8 @@
     public static final class Builder {
         private String mType;
         private Slice mSlice;
-        private PendingIntent mPendingIntent;
-        private Credential mCredential;
+        private PendingIntent mPendingIntent = null;
+        private Credential mCredential = null;
         private boolean mAutoSelectAllowed = false;
 
         /**
@@ -172,9 +172,11 @@
          * {@code credential}, or the {@code pendingIntent}.
          */
         public @NonNull Builder setPendingIntent(@Nullable PendingIntent pendingIntent) {
-            Preconditions.checkState(pendingIntent != null && mCredential != null,
-                    "credential is already set. Cannot set both the pendingIntent "
-                            + "and the credential");
+            if (pendingIntent != null) {
+                Preconditions.checkState(mCredential != null,
+                        "credential is already set. Cannot set both the pendingIntent "
+                                + "and the credential");
+            }
             mPendingIntent = pendingIntent;
             return this;
         }
@@ -186,9 +188,11 @@
          * the {@code pendingIntent}, or the {@code credential}.
          */
         public @NonNull Builder setCredential(@Nullable Credential credential) {
-            Preconditions.checkState(credential != null && mPendingIntent != null,
-                    "pendingIntent is already set. Cannot set both the "
-                            + "pendingIntent and the credential");
+            if (credential != null) {
+                Preconditions.checkState(mPendingIntent != null,
+                        "pendingIntent is already set. Cannot set both the "
+                                + "pendingIntent and the credential");
+            }
             mCredential = credential;
             return this;
         }
diff --git a/core/java/android/service/credentials/CredentialProviderException.java b/core/java/android/service/credentials/CredentialProviderException.java
index b39b4a0..06f0052 100644
--- a/core/java/android/service/credentials/CredentialProviderException.java
+++ b/core/java/android/service/credentials/CredentialProviderException.java
@@ -30,6 +30,22 @@
 public class CredentialProviderException extends Exception {
     public static final int ERROR_UNKNOWN = 0;
 
+    /**
+     * For internal use only.
+     * Error code to be used when the provider request times out.
+     *
+     * @hide
+     */
+    public static final int ERROR_TIMEOUT = 1;
+
+    /**
+     * For internal use only.
+     * Error code to be used when the async task is canceled internally.
+     *
+     * @hide
+     */
+    public static final int ERROR_TASK_CANCELED = 2;
+
     private final int mErrorCode;
 
     /**
@@ -37,6 +53,8 @@
      */
     @IntDef(prefix = {"ERROR_"}, value = {
             ERROR_UNKNOWN,
+            ERROR_TIMEOUT,
+            ERROR_TASK_CANCELED
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface CredentialProviderError { }
diff --git a/core/java/android/service/credentials/CredentialProviderInfo.java b/core/java/android/service/credentials/CredentialProviderInfo.java
index e3f8cb7..2c7a983 100644
--- a/core/java/android/service/credentials/CredentialProviderInfo.java
+++ b/core/java/android/service/credentials/CredentialProviderInfo.java
@@ -18,16 +18,21 @@
 
 import android.Manifest;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.AppGlobals;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
+import android.content.pm.PackageItemInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
 import android.os.RemoteException;
+import android.text.TextUtils;
 import android.util.Log;
 import android.util.Slog;
 
@@ -48,19 +53,21 @@
     @NonNull
     private final List<String> mCapabilities;
 
-    // TODO: Move the two strings below to CredentialProviderService when ready.
-    private static final String CAPABILITY_META_DATA_KEY = "android.credentials.capabilities";
-    private static final String SERVICE_INTERFACE =
-            "android.service.credentials.CredentialProviderService";
-
+    @NonNull
+    private final Context mContext;
+    @Nullable
+    private final Drawable mIcon;
+    @Nullable
+    private final CharSequence mLabel;
 
     /**
      * Constructs an information instance of the credential provider.
      *
-     * @param context The context object
-     * @param serviceComponent The serviceComponent of the provider service
-     * @param userId The android userId for which the current process is running
+     * @param context the context object
+     * @param serviceComponent the serviceComponent of the provider service
+     * @param userId the android userId for which the current process is running
      * @throws PackageManager.NameNotFoundException If provider service is not found
+     * @throws SecurityException If provider does not require the relevant permission
      */
     public CredentialProviderInfo(@NonNull Context context,
             @NonNull ComponentName serviceComponent, int userId)
@@ -68,7 +75,13 @@
         this(context, getServiceInfoOrThrow(serviceComponent, userId));
     }
 
-    private CredentialProviderInfo(@NonNull Context context, @NonNull ServiceInfo serviceInfo) {
+    /**
+     * Constructs an information instance of the credential provider.
+     * @param context the context object
+     * @param serviceInfo the service info for the provider app. This must be retrieved from the
+     *                    {@code PackageManager}
+     */
+    public CredentialProviderInfo(@NonNull Context context, @NonNull ServiceInfo serviceInfo) {
         if (!Manifest.permission.BIND_CREDENTIAL_PROVIDER_SERVICE.equals(serviceInfo.permission)) {
             Log.i(TAG, "Credential Provider Service from : " + serviceInfo.packageName
                     + "does not require permission"
@@ -76,32 +89,43 @@
             throw new SecurityException("Service does not require the expected permission : "
                     + Manifest.permission.BIND_CREDENTIAL_PROVIDER_SERVICE);
         }
+        mContext = context;
         mServiceInfo = serviceInfo;
         mCapabilities = new ArrayList<>();
-        populateProviderCapabilities(context);
+        mIcon = mServiceInfo.loadIcon(mContext.getPackageManager());
+        mLabel = mServiceInfo.loadSafeLabel(
+                mContext.getPackageManager(), 0 /* do not ellipsize */,
+                TextUtils.SAFE_STRING_FLAG_FIRST_LINE | TextUtils.SAFE_STRING_FLAG_TRIM);
+        populateProviderCapabilities(context, serviceInfo);
     }
 
-    private void populateProviderCapabilities(@NonNull Context context) {
-        if (mServiceInfo.applicationInfo.metaData == null) {
-            return;
-        }
+    private void populateProviderCapabilities(@NonNull Context context, ServiceInfo serviceInfo) {
+        final PackageManager pm = context.getPackageManager();
         try {
-            final int resourceId = mServiceInfo.applicationInfo.metaData.getInt(
-                    CAPABILITY_META_DATA_KEY);
-            String[] capabilities = context.getResources().getStringArray(resourceId);
-            if (capabilities == null) {
-                Log.w(TAG, "No capabilities found for provider: " + mServiceInfo.packageName);
+            Bundle metadata = serviceInfo.metaData;
+            Resources resources = pm.getResourcesForApplication(serviceInfo.applicationInfo);
+            if (metadata == null || resources == null) {
+                Log.i(TAG, "populateProviderCapabilities - metadata or resources is null");
                 return;
             }
+
+            String[] capabilities = resources.getStringArray(metadata.getInt(
+                    CredentialProviderService.CAPABILITY_META_DATA_KEY));
+            if (capabilities == null || capabilities.length == 0) {
+                Slog.i(TAG, "No capabilities found for provider:" + serviceInfo.packageName);
+                return;
+            }
+
             for (String capability : capabilities) {
                 if (capability.isEmpty()) {
-                    Log.w(TAG, "Skipping empty capability");
+                    Slog.i(TAG, "Skipping empty capability");
                     continue;
                 }
+                Slog.i(TAG, "Capabilities found for provider: " + capability);
                 mCapabilities.add(capability);
             }
-        } catch (Resources.NotFoundException e) {
-            Log.w(TAG, "Exception while populating provider capabilities: " + e.getMessage());
+        } catch (PackageManager.NameNotFoundException e) {
+            Slog.i(TAG, e.getMessage());
         }
     }
 
@@ -135,6 +159,18 @@
         return mServiceInfo;
     }
 
+    /** Returns the service icon. */
+    @Nullable
+    public Drawable getServiceIcon() {
+        return mIcon;
+    }
+
+    /** Returns the service label. */
+    @Nullable
+    public CharSequence getServiceLabel() {
+        return mLabel;
+    }
+
     /** Returns an immutable list of capabilities this provider service can support. */
     @NonNull
     public List<String> getCapabilities() {
@@ -145,14 +181,15 @@
      * Returns the valid credential provider services available for the user with the
      * given {@code userId}.
      */
+    @NonNull
     public static List<CredentialProviderInfo> getAvailableServices(@NonNull Context context,
             @UserIdInt int userId) {
         final List<CredentialProviderInfo> services = new ArrayList<>();
 
         final List<ResolveInfo> resolveInfos =
                 context.getPackageManager().queryIntentServicesAsUser(
-                        new Intent(SERVICE_INTERFACE),
-                        PackageManager.GET_META_DATA,
+                        new Intent(CredentialProviderService.SERVICE_INTERFACE),
+                        PackageManager.ResolveInfoFlags.of(PackageManager.GET_META_DATA),
                         userId);
         for (ResolveInfo resolveInfo : resolveInfos) {
             final ServiceInfo serviceInfo = resolveInfo.serviceInfo;
@@ -169,8 +206,9 @@
      * Returns the valid credential provider services available for the user, that can
      * support the given {@code credentialType}.
      */
+    @NonNull
     public static List<CredentialProviderInfo> getAvailableServicesForCapability(
-            Context context, @UserIdInt int userId, String credentialType) {
+            @NonNull Context context, @UserIdInt int userId, @NonNull String credentialType) {
         List<CredentialProviderInfo> servicesForCapability = new ArrayList<>();
         final List<CredentialProviderInfo> services = getAvailableServices(context, userId);
 
diff --git a/core/java/android/service/credentials/CredentialProviderService.java b/core/java/android/service/credentials/CredentialProviderService.java
index 1cdf186..b1b08f4 100644
--- a/core/java/android/service/credentials/CredentialProviderService.java
+++ b/core/java/android/service/credentials/CredentialProviderService.java
@@ -42,6 +42,9 @@
  */
 public abstract class CredentialProviderService extends Service {
     private static final String TAG = "CredProviderService";
+
+    public static final String CAPABILITY_META_DATA_KEY = "android.credentials.capabilities";
+
     private Handler mHandler;
 
     /**
@@ -71,12 +74,13 @@
 
     private final ICredentialProviderService mInterface = new ICredentialProviderService.Stub() {
         @Override
-        public void onGetCredentials(GetCredentialsRequest request, ICancellationSignal transport,
+        public ICancellationSignal onGetCredentials(GetCredentialsRequest request,
                 IGetCredentialsCallback callback) {
             Objects.requireNonNull(request);
-            Objects.requireNonNull(transport);
             Objects.requireNonNull(callback);
 
+            ICancellationSignal transport = CancellationSignal.createTransport();
+
             mHandler.sendMessage(obtainMessage(
                     CredentialProviderService::onGetCredentials,
                     CredentialProviderService.this, request,
@@ -100,15 +104,17 @@
                         }
                     }
             ));
+            return transport;
         }
 
         @Override
-        public void onCreateCredential(CreateCredentialRequest request,
-                ICancellationSignal transport, ICreateCredentialCallback callback) {
+        public ICancellationSignal onCreateCredential(CreateCredentialRequest request,
+                ICreateCredentialCallback callback) {
             Objects.requireNonNull(request);
-            Objects.requireNonNull(transport);
             Objects.requireNonNull(callback);
 
+            ICancellationSignal transport = CancellationSignal.createTransport();
+
             mHandler.sendMessage(obtainMessage(
                     CredentialProviderService::onCreateCredential,
                     CredentialProviderService.this, request,
@@ -132,6 +138,7 @@
                         }
                     }
             ));
+            return transport;
         }
     };
 
diff --git a/core/java/android/service/credentials/CredentialsDisplayContent.java b/core/java/android/service/credentials/CredentialsDisplayContent.java
index 2cce169..ab5b524 100644
--- a/core/java/android/service/credentials/CredentialsDisplayContent.java
+++ b/core/java/android/service/credentials/CredentialsDisplayContent.java
@@ -43,12 +43,17 @@
     /** List of provider actions to be displayed on the UI. */
     private final @NonNull List<Action> mActions;
 
+    /** Remote credential entry to get the response from a different device. */
+    private final @Nullable Action mRemoteCredentialEntry;
+
     private CredentialsDisplayContent(@Nullable CharSequence header,
             @NonNull List<CredentialEntry> credentialEntries,
-            @NonNull List<Action> actions) {
+            @NonNull List<Action> actions,
+            @Nullable Action remoteCredentialEntry) {
         mHeader = header;
         mCredentialEntries = credentialEntries;
         mActions = actions;
+        mRemoteCredentialEntry = remoteCredentialEntry;
     }
 
     private CredentialsDisplayContent(@NonNull Parcel in) {
@@ -59,6 +64,7 @@
         List<Action> actions = new ArrayList<>();
         in.readTypedList(actions, Action.CREATOR);
         mActions = actions;
+        mRemoteCredentialEntry = in.readTypedObject(Action.CREATOR);
     }
 
     public static final @NonNull Creator<CredentialsDisplayContent> CREATOR =
@@ -84,6 +90,7 @@
         dest.writeCharSequence(mHeader);
         dest.writeTypedList(mCredentialEntries, flags);
         dest.writeTypedList(mActions, flags);
+        dest.writeTypedObject(mRemoteCredentialEntry, flags);
     }
 
     /**
@@ -108,12 +115,20 @@
     }
 
     /**
+     * Returns the remote credential entry to be displayed on the UI.
+     */
+    public @Nullable Action getRemoteCredentialEntry() {
+        return mRemoteCredentialEntry;
+    }
+
+    /**
      * Builds an instance of {@link CredentialsDisplayContent}.
      */
     public static final class Builder {
-        private CharSequence mHeader = null;
+        private CharSequence mHeader;
         private List<CredentialEntry> mCredentialEntries = new ArrayList<>();
         private List<Action> mActions = new ArrayList<>();
+        private Action mRemoteCredentialEntry;
 
         /**
          * Sets the header to be displayed on the UI.
@@ -124,6 +139,14 @@
         }
 
         /**
+         * Sets the remote credential entry to be displayed on the UI.
+         */
+        public @NonNull Builder setRemoteCredentialEntry(@Nullable Action remoteCredentialEntry) {
+            mRemoteCredentialEntry = remoteCredentialEntry;
+            return this;
+        }
+
+        /**
          * Adds a {@link CredentialEntry} to the list of entries to be displayed on
          * the UI.
          *
@@ -185,7 +208,8 @@
                 throw new IllegalStateException("credentialEntries and actions must not both "
                         + "be empty");
             }
-            return new CredentialsDisplayContent(mHeader, mCredentialEntries, mActions);
+            return new CredentialsDisplayContent(mHeader, mCredentialEntries, mActions,
+                    mRemoteCredentialEntry);
         }
     }
 }
diff --git a/core/java/android/service/credentials/ICredentialProviderService.aidl b/core/java/android/service/credentials/ICredentialProviderService.aidl
index c68430c..c21cefa 100644
--- a/core/java/android/service/credentials/ICredentialProviderService.aidl
+++ b/core/java/android/service/credentials/ICredentialProviderService.aidl
@@ -21,13 +21,14 @@
 import android.service.credentials.CreateCredentialRequest;
 import android.service.credentials.IGetCredentialsCallback;
 import android.service.credentials.ICreateCredentialCallback;
+import android.os.ICancellationSignal;
 
 /**
  * Interface from the system to a credential provider service.
  *
  * @hide
  */
-oneway interface ICredentialProviderService {
-    void onGetCredentials(in GetCredentialsRequest request, in ICancellationSignal transport, in IGetCredentialsCallback callback);
-    void onCreateCredential(in CreateCredentialRequest request, in ICancellationSignal transport, in ICreateCredentialCallback callback);
+interface ICredentialProviderService {
+    ICancellationSignal onGetCredentials(in GetCredentialsRequest request, in IGetCredentialsCallback callback);
+    ICancellationSignal onCreateCredential(in CreateCredentialRequest request, in ICreateCredentialCallback callback);
 }
diff --git a/core/java/android/service/credentials/SaveEntry.java b/core/java/android/service/credentials/SaveEntry.java
index abe51d4..55ff6ff 100644
--- a/core/java/android/service/credentials/SaveEntry.java
+++ b/core/java/android/service/credentials/SaveEntry.java
@@ -17,17 +17,11 @@
 package android.service.credentials;
 
 import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.app.PendingIntent;
 import android.app.slice.Slice;
-import android.credentials.Credential;
 import android.os.Parcel;
 import android.os.Parcelable;
 
-import com.android.internal.util.Preconditions;
-
-import java.util.Objects;
-
 /**
  * An entry to be shown on the UI. This entry represents where the credential to be created will
  * be stored. Examples include user's account, family group etc.
@@ -36,13 +30,11 @@
  */
 public final class SaveEntry implements Parcelable {
     private final @NonNull Slice mSlice;
-    private final @Nullable PendingIntent mPendingIntent;
-    private final @Nullable Credential mCredential;
+    private final @NonNull PendingIntent mPendingIntent;
 
     private SaveEntry(@NonNull Parcel in) {
         mSlice = in.readTypedObject(Slice.CREATOR);
         mPendingIntent = in.readTypedObject(PendingIntent.CREATOR);
-        mCredential = in.readTypedObject(Credential.CREATOR);
     }
 
     public static final @NonNull Creator<SaveEntry> CREATOR = new Creator<SaveEntry>() {
@@ -66,18 +58,23 @@
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeTypedObject(mSlice, flags);
         dest.writeTypedObject(mPendingIntent, flags);
-        dest.writeTypedObject(mCredential, flags);
     }
 
-    /* package-private */ SaveEntry(
+    /**
+     * Constructs a save entry to be displayed on the UI.
+     *
+     * @param slice the display content to be displayed on the UI, along with this entry
+     * @param pendingIntent the intent to be invoked when the user selects this entry
+     */
+    public SaveEntry(
             @NonNull Slice slice,
-            @Nullable PendingIntent pendingIntent,
-            @Nullable Credential credential) {
+            @NonNull PendingIntent pendingIntent) {
         this.mSlice = slice;
         com.android.internal.util.AnnotationValidations.validate(
                 NonNull.class, null, mSlice);
         this.mPendingIntent = pendingIntent;
-        this.mCredential = credential;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mPendingIntent);
     }
 
     /** Returns the content to be displayed with this save entry on the UI. */
@@ -86,76 +83,7 @@
     }
 
     /** Returns the pendingIntent to be invoked when this save entry on the UI is selectcd. */
-    public @Nullable PendingIntent getPendingIntent() {
+    public @NonNull PendingIntent getPendingIntent() {
         return mPendingIntent;
     }
-
-    /** Returns the credential produced by the {@link CreateCredentialRequest}. */
-    public @Nullable Credential getCredential() {
-        return mCredential;
-    }
-
-    /**
-     * A builder for {@link SaveEntry}.
-     */
-    public static final class Builder {
-
-        private @NonNull Slice mSlice;
-        private @Nullable PendingIntent mPendingIntent;
-        private @Nullable Credential mCredential;
-
-        /**
-         * Builds the instance.
-         * @param slice the content to be displayed with this save entry
-         *
-         * @throws NullPointerException If {@code slice} is null.
-         */
-        public Builder(@NonNull Slice slice) {
-            mSlice = Objects.requireNonNull(slice, "slice must not be null");
-        }
-
-        /**
-         * Sets the pendingIntent to be invoked when this entry is selected by the user.
-         *
-         * @throws IllegalStateException If {@code credential} is already set. Must only set either
-         * {@code credential}, or the {@code pendingIntent}.
-         */
-        public @NonNull Builder setPendingIntent(@Nullable PendingIntent pendingIntent) {
-            Preconditions.checkState(pendingIntent != null
-                    && mCredential != null, "credential is already set. Must only set "
-                    + "either the pendingIntent or the credential");
-            mPendingIntent = pendingIntent;
-            return this;
-        }
-
-        /**
-         * Sets the credential to be returned when this entry is selected by the user.
-         *
-         * @throws IllegalStateException If {@code pendingIntent} is already set. Must only
-         * set either the {@code pendingIntent}, or {@code credential}.
-         */
-        public @NonNull Builder setCredential(@Nullable Credential credential) {
-            Preconditions.checkState(credential != null && mPendingIntent != null,
-                    "pendingIntent is already set. Must only set either the credential "
-                            + "or the pendingIntent");
-            mCredential = credential;
-            return this;
-        }
-
-        /**
-         * Builds the instance.
-         *
-         * @throws IllegalStateException if both {@code pendingIntent} and {@code credential}
-         * are null.
-         */
-        public @NonNull SaveEntry build() {
-            Preconditions.checkState(mPendingIntent == null && mCredential == null,
-                    "pendingIntent and credential both must not be null. Must set "
-                            + "either the pendingIntnet or the credential");
-            return new SaveEntry(
-                    mSlice,
-                    mPendingIntent,
-                    mCredential);
-        }
-    }
 }
diff --git a/core/java/android/service/dreams/DreamManagerInternal.java b/core/java/android/service/dreams/DreamManagerInternal.java
index 295171c..5f30ad0 100644
--- a/core/java/android/service/dreams/DreamManagerInternal.java
+++ b/core/java/android/service/dreams/DreamManagerInternal.java
@@ -54,6 +54,13 @@
     public abstract void requestDream();
 
     /**
+     * Whether dreaming can start given user settings and the current dock/charge state.
+     *
+     * @param isScreenOn True if the screen is currently on.
+     */
+    public abstract boolean canStartDreaming(boolean isScreenOn);
+
+    /**
      * Called by the ActivityTaskManagerService to verify that the startDreamActivity
      * request comes from the current active dream component.
      *
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index c6cd708..37fc9f2 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -575,6 +575,7 @@
          */
         public void reportEngineShown(boolean waitForEngineShown) {
             if (mIWallpaperEngine.mShownReported) return;
+            Log.d(TAG, "reportEngineShown: shouldWait=" + waitForEngineShown);
             if (!waitForEngineShown) {
                 Message message = mCaller.obtainMessage(MSG_REPORT_SHOWN);
                 mCaller.removeMessages(MSG_REPORT_SHOWN);
diff --git a/core/java/android/text/GraphemeClusterSegmentFinder.java b/core/java/android/text/GraphemeClusterSegmentFinder.java
index 3335751..656774f 100644
--- a/core/java/android/text/GraphemeClusterSegmentFinder.java
+++ b/core/java/android/text/GraphemeClusterSegmentFinder.java
@@ -49,6 +49,7 @@
 
     @Override
     public int previousStartBoundary(@IntRange(from = 0) int offset) {
+        if (offset == 0) return DONE;
         int boundary = mTextPaint.getTextRunCursor(
                 mText, 0, mText.length(), false, offset, Paint.CURSOR_BEFORE);
         return boundary == -1 ? DONE : boundary;
@@ -56,6 +57,7 @@
 
     @Override
     public int previousEndBoundary(@IntRange(from = 0) int offset) {
+        if (offset == 0) return DONE;
         int boundary = mTextPaint.getTextRunCursor(
                 mText, 0, mText.length(), false, offset, Paint.CURSOR_BEFORE);
         // Check that there is another cursor position before, otherwise this is not a valid
@@ -69,6 +71,7 @@
 
     @Override
     public int nextStartBoundary(@IntRange(from = 0) int offset) {
+        if (offset == mText.length()) return DONE;
         int boundary = mTextPaint.getTextRunCursor(
                 mText, 0, mText.length(), false, offset, Paint.CURSOR_AFTER);
         // Check that there is another cursor position after, otherwise this is not a valid
@@ -82,6 +85,7 @@
 
     @Override
     public int nextEndBoundary(@IntRange(from = 0) int offset) {
+        if (offset == mText.length()) return DONE;
         int boundary = mTextPaint.getTextRunCursor(
                 mText, 0, mText.length(), false, offset, Paint.CURSOR_AFTER);
         return boundary == -1 ? DONE : boundary;
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 1337d6a..54ec07e 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -3000,6 +3000,18 @@
         }
 
         /**
+         * Returns the BiDi level of this run.
+         *
+         * @param runIndex the index of the BiDi run
+         * @return the BiDi level of this run.
+         * @hide
+         */
+        @IntRange(from = 0)
+        public int getRunLevel(int runIndex) {
+            return (mDirections[runIndex * 2 + 1] >>> RUN_LEVEL_SHIFT) & RUN_LEVEL_MASK;
+        }
+
+        /**
          * Returns true if the BiDi run is RTL.
          *
          * @param runIndex the index of the BiDi run
diff --git a/core/java/android/text/SegmentFinder.java b/core/java/android/text/SegmentFinder.java
index c21c577..be0094b 100644
--- a/core/java/android/text/SegmentFinder.java
+++ b/core/java/android/text/SegmentFinder.java
@@ -19,6 +19,13 @@
 import android.annotation.IntRange;
 import android.graphics.RectF;
 
+import androidx.annotation.NonNull;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Arrays;
+import java.util.Objects;
+
 /**
  * Finds text segment boundaries within text. Subclasses can implement different types of text
  * segments. Grapheme clusters and words are examples of possible text segments. These are
@@ -63,4 +70,144 @@
      * character offset, or {@code DONE} if there are none.
      */
     public abstract int nextEndBoundary(@IntRange(from = 0) int offset);
+
+    /**
+     * The default {@link SegmentFinder} implementation based on given segment ranges.
+     */
+    public static class DefaultSegmentFinder extends SegmentFinder {
+        private final int[] mSegments;
+
+        /**
+         * Create a SegmentFinder with segments stored in an array, where i-th segment's start is
+         * stored at segments[2 * i] and end is stored at segments[2 * i + 1] respectively.
+         *
+         * <p> It is required that segments do not overlap, and are already sorted by their start
+         * indices. </p>
+         * @param segments the array that stores the segment ranges.
+         * @throws IllegalArgumentException if the given segments array's length is not even; the
+         * given segments are not sorted or there are segments overlap with others.
+         */
+        public DefaultSegmentFinder(@NonNull int[] segments) {
+            checkSegmentsValid(segments);
+            mSegments = segments;
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public int previousStartBoundary(@IntRange(from = 0) int offset) {
+            return findPrevious(offset, /* isStart = */ true);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public int previousEndBoundary(@IntRange(from = 0) int offset) {
+            return findPrevious(offset, /* isStart = */ false);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public int nextStartBoundary(@IntRange(from = 0) int offset) {
+            return findNext(offset, /* isStart = */ true);
+        }
+
+        /** {@inheritDoc} */
+        @Override
+        public int nextEndBoundary(@IntRange(from = 0) int offset) {
+            return findNext(offset, /* isStart = */ false);
+        }
+
+        private int findNext(int offset, boolean isStart) {
+            if (offset < 0) return DONE;
+            if (mSegments.length < 1 || offset > mSegments[mSegments.length - 1]) return DONE;
+
+            if (offset < mSegments[0]) {
+                return isStart ? mSegments[0] : mSegments[1];
+            }
+
+            int index = Arrays.binarySearch(mSegments, offset);
+            if (index >= 0) {
+                // mSegments may have duplicate elements (The previous segments end equals
+                // to the following segments start.) Move the index forwards since we are searching
+                // for the next segment.
+                if (index + 1 < mSegments.length && mSegments[index + 1] == offset) {
+                    index = index + 1;
+                }
+                // Point the index to the first segment boundary larger than the given offset.
+                index += 1;
+            } else {
+                // binarySearch returns the insertion point, it's the first segment boundary larger
+                // than the given offset.
+                index = -(index + 1);
+            }
+            if (index >= mSegments.length) return DONE;
+
+            //  +---------------------------------------+
+            //  |               | isStart   | isEnd     |
+            //  |---------------+-----------+-----------|
+            //  | indexIsStart  | index     | index + 1 |
+            //  |---------------+-----------+-----------|
+            //  | indexIsEnd    | index + 1 | index     |
+            //  +---------------------------------------+
+            boolean indexIsStart = index % 2 == 0;
+            if (isStart != indexIsStart) {
+                return (index + 1 < mSegments.length) ? mSegments[index + 1] : DONE;
+            }
+            return mSegments[index];
+        }
+
+        private int findPrevious(int offset, boolean isStart) {
+            if (mSegments.length < 1 || offset < mSegments[0]) return DONE;
+
+            if (offset > mSegments[mSegments.length - 1]) {
+                return isStart ? mSegments[mSegments.length - 2] : mSegments[mSegments.length - 1];
+            }
+
+            int index = Arrays.binarySearch(mSegments, offset);
+            if (index >= 0) {
+                // mSegments may have duplicate elements (when the previous segments end equal
+                // to the following segments start). Move the index backwards since we are searching
+                // for the previous segment.
+                if (index > 0 && mSegments[index - 1] == offset) {
+                    index = index - 1;
+                }
+                // Point the index to the first segment boundary smaller than the given offset.
+                index -= 1;
+            } else {
+                // binarySearch returns the insertion point, insertionPoint - 1 is the first
+                // segment boundary smaller than the given offset.
+                index = -(index + 1) - 1;
+            }
+            if (index < 0) return DONE;
+
+            //  +---------------------------------------+
+            //  |               | isStart   | isEnd     |
+            //  |---------------+-----------+-----------|
+            //  | indexIsStart  | index     | index - 1 |
+            //  |---------------+-----------+-----------|
+            //  | indexIsEnd    | index - 1 | index     |
+            //  +---------------------------------------+
+            boolean indexIsStart = index % 2 == 0;
+            if (isStart != indexIsStart) {
+                return (index > 0) ? mSegments[index - 1] : DONE;
+            }
+            return mSegments[index];
+        }
+
+        private static void checkSegmentsValid(int[] segments) {
+            Objects.requireNonNull(segments);
+            Preconditions.checkArgument(segments.length % 2 == 0,
+                    "the length of segments must be even");
+            if (segments.length == 0) return;
+            int lastSegmentEnd = Integer.MIN_VALUE;
+            for (int index = 0; index < segments.length; index += 2) {
+                if (segments[index] < lastSegmentEnd) {
+                    throw new IllegalArgumentException("segments can't overlap");
+                }
+                if (segments[index] >= segments[index + 1]) {
+                    throw new IllegalArgumentException("the segment range can't be empty");
+                }
+                lastSegmentEnd = segments[index + 1];
+            }
+        }
+    }
 }
diff --git a/core/java/android/text/TextUtils.java b/core/java/android/text/TextUtils.java
index 596e491..ff66e5f 100644
--- a/core/java/android/text/TextUtils.java
+++ b/core/java/android/text/TextUtils.java
@@ -2331,7 +2331,8 @@
         return trimmed;
     }
 
-    private static boolean isNewline(int codePoint) {
+    /** @hide */
+    public static boolean isNewline(int codePoint) {
         int type = Character.getType(codePoint);
         return type == Character.PARAGRAPH_SEPARATOR || type == Character.LINE_SEPARATOR
                 || codePoint == LINE_FEED_CODE_POINT;
diff --git a/core/java/android/text/method/BaseKeyListener.java b/core/java/android/text/method/BaseKeyListener.java
index d4bcd12..01989d5 100644
--- a/core/java/android/text/method/BaseKeyListener.java
+++ b/core/java/android/text/method/BaseKeyListener.java
@@ -229,6 +229,8 @@
                         break;
                     } else if (Emoji.isEmojiModifierBase(codePoint)) {
                         deleteCharCount += Character.charCount(codePoint);
+                        state = STATE_BEFORE_EMOJI;
+                        break;
                     }
                     state = STATE_FINISHED;
                     break;
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index d1f05ec..7b6a6d2 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -103,6 +103,25 @@
     public static final String SETTINGS_NEW_KEYBOARD_UI = "settings_new_keyboard_ui";
 
     /**
+     * Enable new shortcut list UI
+     * @hide
+     */
+    public static final String SETTINGS_NEW_KEYBOARD_SHORTCUT = "settings_new_keyboard_shortcut";
+
+    /**
+     * Enable new modifier key settings UI
+     * @hide
+     */
+    public static final String SETTINGS_NEW_KEYBOARD_MODIFIER_KEY =
+            "settings_new_keyboard_modifier_key";
+
+    /**
+     * Enable new trackpad settings UI
+     * @hide
+     */
+    public static final String SETTINGS_NEW_KEYBOARD_TRACKPAD = "settings_new_keyboard_trackpad";
+
+    /**
      * Enable the new pages which is implemented with SPA.
      * @hide
      */
@@ -143,6 +162,9 @@
         DEFAULT_FLAGS.put(SETTINGS_HIDE_SECOND_LAYER_PAGE_NAVIGATE_UP_BUTTON_IN_TWO_PANE, "true");
         DEFAULT_FLAGS.put(SETTINGS_AUTO_TEXT_WRAPPING, "false");
         DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_UI, "false");
+        DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_SHORTCUT, "false");
+        DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_MODIFIER_KEY, "false");
+        DEFAULT_FLAGS.put(SETTINGS_NEW_KEYBOARD_TRACKPAD, "false");
         DEFAULT_FLAGS.put(SETTINGS_ENABLE_SPA, "false");
         DEFAULT_FLAGS.put(SETTINGS_ADB_METRICS_WRITER, "false");
     }
@@ -158,6 +180,9 @@
         PERSISTENT_FLAGS.add(SETTINGS_HIDE_SECOND_LAYER_PAGE_NAVIGATE_UP_BUTTON_IN_TWO_PANE);
         PERSISTENT_FLAGS.add(SETTINGS_AUTO_TEXT_WRAPPING);
         PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_UI);
+        PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_SHORTCUT);
+        PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_MODIFIER_KEY);
+        PERSISTENT_FLAGS.add(SETTINGS_NEW_KEYBOARD_TRACKPAD);
     }
 
     /**
diff --git a/core/java/android/util/PackageUtils.java b/core/java/android/util/PackageUtils.java
index 1148120..6f8c5db 100644
--- a/core/java/android/util/PackageUtils.java
+++ b/core/java/android/util/PackageUtils.java
@@ -224,7 +224,7 @@
         byte[] resultBytes = messageDigest.digest();
 
         if (separator == null) {
-            return HexEncoding.encodeToString(resultBytes, true);
+            return HexEncoding.encodeToString(resultBytes, false);
         }
 
         int length = resultBytes.length;
diff --git a/core/java/android/view/HandwritingDelegateConfiguration.java b/core/java/android/view/HandwritingDelegateConfiguration.java
new file mode 100644
index 0000000..719c614
--- /dev/null
+++ b/core/java/android/view/HandwritingDelegateConfiguration.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+import android.annotation.IdRes;
+import android.annotation.NonNull;
+
+/**
+ * Configuration for a view to act as a handwriting initiation delegate. This allows handwriting
+ * mode for a delegator editor view to be initiated by stylus movement on the delegate view.
+ *
+ * <p>If a stylus {@link MotionEvent} occurs within the delegate view's bounds, the callback
+ * returned by {@link #getInitiationCallback()} will be called. The callback implementation is
+ * expected to show and focus the delegator editor view. If a view with identifier matching {@link
+ * #getDelegatorViewId()} creates an input connection while the same stylus {@link MotionEvent}
+ * sequence is ongoing, handwriting mode will be initiated for that view.
+ *
+ * <p>A common use case is a custom view which looks like a text editor but does not actually
+ * support text editing itself, and clicking on the custom view causes an EditText to be shown. To
+ * support handwriting initiation in this case, {@link View#setHandwritingDelegateConfiguration} can
+ * be called on the custom view to configure it as a delegate, and set the EditText as the delegator
+ * by passing the EditText's identifier as the {@code delegatorViewId}. The {@code
+ * initiationCallback} implementation is typically the same as the click listener implementation
+ * which shows the EditText.
+ */
+public class HandwritingDelegateConfiguration {
+    @IdRes private final int mDelegatorViewId;
+    @NonNull private final Runnable mInitiationCallback;
+
+    /**
+     * Constructs a HandwritingDelegateConfiguration instance.
+     *
+     * @param delegatorViewId identifier of the delegator editor view for which handwriting mode
+     *     should be initiated
+     * @param initiationCallback callback called when a stylus {@link MotionEvent} occurs within
+     *     this view's bounds. This will be called from the UI thread.
+     */
+    public HandwritingDelegateConfiguration(
+            @IdRes int delegatorViewId, @NonNull Runnable initiationCallback) {
+        mDelegatorViewId = delegatorViewId;
+        mInitiationCallback = initiationCallback;
+    }
+
+    /**
+     * Returns the identifier of the delegator editor view for which handwriting mode should be
+     * initiated.
+     */
+    public int getDelegatorViewId() {
+        return mDelegatorViewId;
+    }
+
+    /**
+     * Returns the callback which should be called when a stylus {@link MotionEvent} occurs within
+     * the delegate view's bounds. The callback should only be called from the UI thread.
+     */
+    @NonNull
+    public Runnable getInitiationCallback() {
+        return mInitiationCallback;
+    }
+}
diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java
index a0a07b3..2e4073e 100644
--- a/core/java/android/view/HandwritingInitiator.java
+++ b/core/java/android/view/HandwritingInitiator.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import android.annotation.IdRes;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.graphics.Rect;
@@ -161,6 +162,15 @@
                     if (candidateView != null) {
                         if (candidateView == getConnectedView()) {
                             startHandwriting(candidateView);
+                        } else if (candidateView.getHandwritingDelegateConfiguration() != null) {
+                            mState.mDelegatorViewId =
+                                    candidateView
+                                            .getHandwritingDelegateConfiguration()
+                                            .getDelegatorViewId();
+                            candidateView
+                                    .getHandwritingDelegateConfiguration()
+                                    .getInitiationCallback()
+                                    .run();
                         } else {
                             if (candidateView.getRevealOnFocusHint()) {
                                 candidateView.setRevealOnFocusHint(false);
@@ -259,8 +269,10 @@
         }
 
         final Rect handwritingArea = getViewHandwritingArea(connectedView);
-        if (isInHandwritingArea(handwritingArea, mState.mStylusDownX,
-                mState.mStylusDownY, connectedView)) {
+        if ((mState.mDelegatorViewId != View.NO_ID
+                        && mState.mDelegatorViewId == connectedView.getId())
+                || isInHandwritingArea(
+                        handwritingArea, mState.mStylusDownX, mState.mStylusDownY, connectedView)) {
             startHandwriting(connectedView);
         } else {
             mState.mShouldInitHandwriting = false;
@@ -287,6 +299,11 @@
         if (!view.isAutoHandwritingEnabled()) {
             return false;
         }
+        // The view may be a handwriting initiation delegate, in which case it is not the editor
+        // view for which handwriting would be started. However, in almost all cases, the return
+        // values of View#isStylusHandwritingAvailable will be the same for the delegate view and
+        // the delegator editor view. So the delegate view can be used to decide whether handwriting
+        // should be triggered.
         return view.isStylusHandwritingAvailable();
     }
 
@@ -473,6 +490,13 @@
          * built InputConnection.
          */
         private boolean mExceedHandwritingSlop;
+        /**
+         * If the current ongoing stylus MotionEvent sequence started over a handwriting initiation
+         * delegate view, then this is the view identifier of the corresponding delegator view. If
+         * the delegator view creates an input connection while the MotionEvent sequence is still
+         * ongoing, then handwriting mode will be initiated for the delegator view.
+         */
+        @IdRes private int mDelegatorViewId = View.NO_ID;
 
         /** The pointer id of the stylus pointer that is being tracked. */
         private final int mStylusPointerId;
diff --git a/core/java/android/view/InputDevice.java b/core/java/android/view/InputDevice.java
index 9b1d867..799955b 100644
--- a/core/java/android/view/InputDevice.java
+++ b/core/java/android/view/InputDevice.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import android.Manifest;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -1010,6 +1011,22 @@
     }
 
     /**
+     * Returns the Bluetooth address of this input device, if known.
+     *
+     * The returned string is always null if this input device is not connected
+     * via Bluetooth, or if the Bluetooth address of the device cannot be
+     * determined. The returned address will look like: "11:22:33:44:55:66".
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.BLUETOOTH)
+    @Nullable
+    public String getBluetoothAddress() {
+        // We query the address via a separate InputManager API instead of pre-populating it in
+        // this class to avoid leaking it to apps that do not have sufficient permissions.
+        return InputManager.getInstance().getInputDeviceBluetoothAddress(mId);
+    }
+
+    /**
      * Gets the vibrator service associated with the device, if there is one.
      * Even if the device does not have a vibrator, the result is never null.
      * Use {@link Vibrator#hasVibrator} to determine whether a vibrator is
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 198ac9d..b24303b 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -121,16 +121,23 @@
     private static final boolean DEBUG = false;
     private static final boolean DEBUG_POSITION = false;
 
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(
+            maxTargetSdk = Build.VERSION_CODES.TIRAMISU,
+            publicAlternatives = "Track {@link SurfaceHolder#addCallback} instead")
     final ArrayList<SurfaceHolder.Callback> mCallbacks = new ArrayList<>();
 
     final int[] mLocation = new int[2];
 
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(
+            maxTargetSdk = Build.VERSION_CODES.TIRAMISU,
+            publicAlternatives = "Use {@link SurfaceHolder#lockCanvas} instead")
     final ReentrantLock mSurfaceLock = new ReentrantLock();
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(
+            maxTargetSdk = Build.VERSION_CODES.TIRAMISU,
+            publicAlternatives = "Use {@link SurfaceHolder#getSurface} instead")
     final Surface mSurface = new Surface();       // Current surface in use
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023,
+                         publicAlternatives = "Use {@link View#getVisibility} instead")
     boolean mDrawingStopped = true;
     // We use this to track if the application has produced a frame
     // in to the Surface. Up until that point, we should be careful not to punch
@@ -156,13 +163,16 @@
     int mSubLayer = APPLICATION_MEDIA_SUBLAYER;
     int mRequestedSubLayer = APPLICATION_MEDIA_SUBLAYER;
 
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023,
+                         publicAlternatives = "Use {@link SurfaceHolder#isCreating} instead")
     boolean mIsCreating = false;
 
     private final ViewTreeObserver.OnScrollChangedListener mScrollChangedListener =
             this::updateSurface;
 
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(
+            maxTargetSdk = Build.VERSION_CODES.TIRAMISU,
+            publicAlternatives = "Rely on {@link ViewTreeObserver#dispatchOnPreDraw} instead")
     private final ViewTreeObserver.OnPreDrawListener mDrawListener = () -> {
         // reposition ourselves where the surface is
         mHaveFrame = getWidth() > 0 && getHeight() > 0;
@@ -176,24 +186,32 @@
     boolean mViewVisibility = false;
     boolean mWindowStopped = false;
 
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023,
+                         publicAlternatives = "Use {@link View#getWidth} instead")
     int mRequestedWidth = -1;
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023,
+                         publicAlternatives = "Use {@link View#getHeight} instead")
     int mRequestedHeight = -1;
     /* Set SurfaceView's format to 565 by default to maintain backward
      * compatibility with applications assuming this format.
      */
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(
+            maxTargetSdk = Build.VERSION_CODES.TIRAMISU,
+            publicAlternatives = "Use {@code SurfaceHolder.Callback#surfaceChanged} instead")
     int mRequestedFormat = PixelFormat.RGB_565;
 
     float mAlpha = 1f;
     boolean mClipSurfaceToBounds;
     int mBackgroundColor = Color.BLACK;
 
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(
+            maxTargetSdk = Build.VERSION_CODES.TIRAMISU,
+            publicAlternatives = "Use {@link View#getWidth} and {@link View#getHeight} to "
+                    + "determine if the SurfaceView is onscreen and has a frame")
     boolean mHaveFrame = false;
     boolean mSurfaceCreated = false;
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023,
+                         publicAlternatives = "Time {@link SurfaceHolder#lockCanvas} instead")
     long mLastLockTime = 0;
 
     boolean mVisible = false;
@@ -202,9 +220,13 @@
     int mSurfaceWidth = -1;
     int mSurfaceHeight = -1;
     float mCornerRadius;
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(
+            maxTargetSdk = Build.VERSION_CODES.TIRAMISU,
+            publicAlternatives = "Use {@code SurfaceHolder.Callback#surfaceChanged} "
+            + "instead")
     int mFormat = -1;
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023,
+                         publicAlternatives = "Use {@link SurfaceHolder#getSurfaceFrame} instead")
     final Rect mSurfaceFrame = new Rect();
     int mLastSurfaceWidth = -1, mLastSurfaceHeight = -1;
     @SurfaceControl.BufferTransform int mTransformHint = 0;
@@ -1410,7 +1432,9 @@
      * @return true if the surface has dimensions that are fixed in size
      * @hide
      */
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(
+            maxTargetSdk = Build.VERSION_CODES.TIRAMISU,
+            publicAlternatives = "Track {@link SurfaceHolder#setFixedSize} instead")
     public boolean isFixedSize() {
         return (mRequestedWidth != -1 || mRequestedHeight != -1);
     }
@@ -1446,7 +1470,9 @@
         updateBackgroundColor(t);
     }
 
-    @UnsupportedAppUsage
+    @UnsupportedAppUsage(
+            maxTargetSdk = Build.VERSION_CODES.TIRAMISU,
+            publicAlternatives = "Use {@link SurfaceView#getHolder} instead")
     private final SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
         private static final String LOG_TAG = "SurfaceHolder";
 
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 7b6ebf7..2b071db 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -5063,6 +5063,13 @@
     private boolean mHoveringTouchDelegate = false;
 
     /**
+     * Configuration for this view to act as a handwriting initiation delegate. This allows
+     * handwriting mode for a delegator editor view to be initiated by stylus movement on this
+     * delegate view.
+     */
+    private HandwritingDelegateConfiguration mHandwritingDelegateConfiguration;
+
+    /**
      * Solid color to use as a background when creating the drawing cache. Enables
      * the cache to use 16 bit bitmaps instead of 32 bit.
      */
@@ -12255,6 +12262,30 @@
     }
 
     /**
+     * Configures this view to act as a handwriting initiation delegate. This allows handwriting
+     * mode for a delegator editor view to be initiated by stylus movement on this delegate view.
+     *
+     * <p>If {@code null} is passed, this view will no longer act as a handwriting initiation
+     * delegate.
+     */
+    public void setHandwritingDelegateConfiguration(
+            @Nullable HandwritingDelegateConfiguration configuration) {
+        mHandwritingDelegateConfiguration = configuration;
+        if (configuration != null) {
+            setHandwritingArea(new Rect(0, 0, getWidth(), getHeight()));
+        }
+    }
+
+    /**
+     * If this view has been configured as a handwriting initiation delegate, returns the delegate
+     * configuration.
+     */
+    @Nullable
+    public HandwritingDelegateConfiguration getHandwritingDelegateConfiguration() {
+        return mHandwritingDelegateConfiguration;
+    }
+
+    /**
      * Gets the coordinates of this view in the coordinate space of the
      * {@link Surface} that contains the view.
      *
@@ -17403,7 +17434,7 @@
      * (but after its own view has been drawn).
      * @param canvas the canvas on which to draw the view
      */
-    protected void dispatchDraw(Canvas canvas) {
+    protected void dispatchDraw(@NonNull Canvas canvas) {
 
     }
 
@@ -20688,7 +20719,7 @@
         out.bottom = mScrollY + mBottom - mTop;
     }
 
-    private void onDrawScrollIndicators(Canvas c) {
+    private void onDrawScrollIndicators(@NonNull Canvas c) {
         if ((mPrivateFlags3 & SCROLL_INDICATORS_PFLAG3_MASK) == 0) {
             // No scroll indicators enabled.
             return;
@@ -20872,7 +20903,7 @@
      *
      * @see #awakenScrollBars(int)
      */
-    protected final void onDrawScrollBars(Canvas canvas) {
+    protected final void onDrawScrollBars(@NonNull Canvas canvas) {
         // scrollbars are drawn only when the animation is running
         final ScrollabilityCache cache = mScrollCache;
 
@@ -20984,7 +21015,7 @@
      * @hide
      */
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
-    protected void onDrawHorizontalScrollBar(Canvas canvas, Drawable scrollBar,
+    protected void onDrawHorizontalScrollBar(@NonNull Canvas canvas, Drawable scrollBar,
             int l, int t, int r, int b) {
         scrollBar.setBounds(l, t, r, b);
         scrollBar.draw(canvas);
@@ -21004,7 +21035,7 @@
      * @hide
      */
     @UnsupportedAppUsage
-    protected void onDrawVerticalScrollBar(Canvas canvas, Drawable scrollBar,
+    protected void onDrawVerticalScrollBar(@NonNull Canvas canvas, Drawable scrollBar,
             int l, int t, int r, int b) {
         scrollBar.setBounds(l, t, r, b);
         scrollBar.draw(canvas);
@@ -21015,7 +21046,7 @@
      *
      * @param canvas the canvas on which the background will be drawn
      */
-    protected void onDraw(Canvas canvas) {
+    protected void onDraw(@NonNull Canvas canvas) {
     }
 
     /*
@@ -23130,7 +23161,7 @@
      *
      * @hide
      */
-    protected final boolean drawsWithRenderNode(Canvas canvas) {
+    protected final boolean drawsWithRenderNode(@NonNull Canvas canvas) {
         return mAttachInfo != null
                 && mAttachInfo.mHardwareAccelerated
                 && canvas.isHardwareAccelerated();
@@ -23142,7 +23173,7 @@
      * This is where the View specializes rendering behavior based on layer type,
      * and hardware acceleration.
      */
-    boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
+    boolean draw(@NonNull Canvas canvas, ViewGroup parent, long drawingTime) {
 
         final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated();
 
@@ -23430,7 +23461,7 @@
         return (int) (dips * scale + 0.5f);
     }
 
-    final private void debugDrawFocus(Canvas canvas) {
+    private void debugDrawFocus(@NonNull Canvas canvas) {
         if (isFocused()) {
             final int cornerSquareSize = dipsToPixels(DEBUG_CORNERS_SIZE_DIP);
             final int l = mScrollX;
@@ -23465,7 +23496,7 @@
      * @param canvas The Canvas to which the View is rendered.
      */
     @CallSuper
-    public void draw(Canvas canvas) {
+    public void draw(@NonNull Canvas canvas) {
         final int privateFlags = mPrivateFlags;
         mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
 
@@ -23700,7 +23731,7 @@
      * @param canvas Canvas on which to draw the background
      */
     @UnsupportedAppUsage
-    private void drawBackground(Canvas canvas) {
+    private void drawBackground(@NonNull Canvas canvas) {
         final Drawable background = mBackground;
         if (background == null) {
             return;
@@ -24205,7 +24236,7 @@
             }
         }
         rebuildOutline();
-        if (onCheckIsTextEditor()) {
+        if (onCheckIsTextEditor() || mHandwritingDelegateConfiguration != null) {
             setHandwritingArea(new Rect(0, 0, newWidth, newHeight));
         }
     }
@@ -24600,7 +24631,7 @@
      * Draw the default focus highlight onto the canvas if there is one and this view is focused.
      * @param canvas the canvas where we're drawing the highlight.
      */
-    private void drawDefaultFocusHighlight(Canvas canvas) {
+    private void drawDefaultFocusHighlight(@NonNull Canvas canvas) {
         if (mDefaultFocusHighlight != null && isFocused()) {
             if (mDefaultFocusHighlightSizeChanged) {
                 mDefaultFocusHighlightSizeChanged = false;
@@ -25398,7 +25429,7 @@
      *
      * @param canvas canvas to draw into
      */
-    public void onDrawForeground(Canvas canvas) {
+    public void onDrawForeground(@NonNull Canvas canvas) {
         onDrawScrollIndicators(canvas);
         onDrawScrollBars(canvas);
 
@@ -27456,7 +27487,7 @@
          *
          * @param canvas A {@link android.graphics.Canvas} object in which to draw the shadow image.
          */
-        public void onDrawShadow(Canvas canvas) {
+        public void onDrawShadow(@NonNull Canvas canvas) {
             final View view = mView.get();
             if (view != null) {
                 view.draw(canvas);
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index c4f20dc..46b2cfc 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -4166,7 +4166,7 @@
     /**
      * @hide
      */
-    protected void onDebugDrawMargins(Canvas canvas, Paint paint) {
+    protected void onDebugDrawMargins(@NonNull Canvas canvas, Paint paint) {
         for (int i = 0; i < getChildCount(); i++) {
             View c = getChildAt(i);
             c.getLayoutParams().onDebugDraw(c, canvas, paint);
@@ -4176,7 +4176,7 @@
     /**
      * @hide
      */
-    protected void onDebugDraw(Canvas canvas) {
+    protected void onDebugDraw(@NonNull Canvas canvas) {
         Paint paint = getDebugPaint();
 
         // Draw optical bounds
@@ -4224,7 +4224,7 @@
     }
 
     @Override
-    protected void dispatchDraw(Canvas canvas) {
+    protected void dispatchDraw(@NonNull Canvas canvas) {
         final int childrenCount = mChildrenCount;
         final View[] children = mChildren;
         int flags = mGroupFlags;
@@ -4533,7 +4533,7 @@
      * @param drawingTime The time at which draw is occurring
      * @return True if an invalidate() was issued
      */
-    protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
+    protected boolean drawChild(@NonNull Canvas canvas, View child, long drawingTime) {
         return child.draw(canvas, this, drawingTime);
     }
 
@@ -9208,7 +9208,8 @@
         }
     }
 
-    private static void drawRect(Canvas canvas, Paint paint, int x1, int y1, int x2, int y2) {
+    private static void drawRect(@NonNull Canvas canvas, Paint paint, int x1, int y1,
+                                 int x2, int y2) {
         if (sDebugLines== null) {
             // TODO: This won't work with multiple UI threads in a single process
             sDebugLines = new float[16];
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index ff4588a..e664ebf 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -1257,7 +1257,7 @@
                     mTmpFrames.attachedFrame = attachedFrame;
                     mTmpFrames.sizeCompatScale = sizeCompatScale[0];
                     mInvSizeCompatScale = 1f / sizeCompatScale[0];
-                } catch (RemoteException e) {
+                } catch (RemoteException | RuntimeException e) {
                     mAdded = false;
                     mView = null;
                     mAttachInfo.mRootView = null;
diff --git a/core/java/android/view/accessibility/AccessibilityDisplayProxy.java b/core/java/android/view/accessibility/AccessibilityDisplayProxy.java
new file mode 100644
index 0000000..85f5056
--- /dev/null
+++ b/core/java/android/view/accessibility/AccessibilityDisplayProxy.java
@@ -0,0 +1,181 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.accessibility;
+
+import android.accessibilityservice.AccessibilityGestureEvent;
+import android.accessibilityservice.AccessibilityService;
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.IAccessibilityServiceClient;
+import android.accessibilityservice.MagnificationConfig;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.content.Context;
+import android.graphics.Region;
+import android.os.IBinder;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.inputmethod.EditorInfo;
+
+import com.android.internal.inputmethod.IAccessibilityInputMethodSessionCallback;
+import com.android.internal.inputmethod.RemoteAccessibilityInputConnection;
+
+import java.util.List;
+import java.util.concurrent.Executor;
+
+/**
+ * Allows a privileged app - an app with MANAGE_ACCESSIBILITY permission and SystemAPI access - to
+ * interact with the windows in the display that this proxy represents. Proxying the default display
+ * or a display that is not tracked will throw an exception. Only the real user has access to global
+ * clients like SystemUI.
+ *
+ * <p>
+ * To register and unregister a proxy, use
+ * {@link AccessibilityManager#registerDisplayProxy(AccessibilityDisplayProxy)}
+ * and {@link AccessibilityManager#unregisterDisplayProxy(AccessibilityDisplayProxy)}. If the app
+ * that has registered the proxy dies, the system will remove the proxy.
+ *
+ * TODO(241429275): Complete proxy impl and add additional support (if necessary) like cache methods
+ * @hide
+ */
+@SystemApi
+public abstract class AccessibilityDisplayProxy {
+    private static final String LOG_TAG = "AccessibilityDisplayProxy";
+    private static final int INVALID_CONNECTION_ID = -1;
+
+    private List<AccessibilityServiceInfo> mInstalledAndEnabledServices;
+    private Executor mExecutor;
+    private int mConnectionId = INVALID_CONNECTION_ID;
+    private int mDisplayId;
+    IAccessibilityServiceClient mServiceClient;
+
+    /**
+     * Constructs an AccessibilityDisplayProxy instance.
+     * @param displayId the id of the display to proxy.
+     * @param executor the executor used to execute proxy callbacks.
+     * @param installedAndEnabledServices the list of infos representing the installed and
+     *                                    enabled a11y services.
+     */
+    public AccessibilityDisplayProxy(int displayId, @NonNull Executor executor,
+            @NonNull List<AccessibilityServiceInfo> installedAndEnabledServices) {
+        mDisplayId = displayId;
+        mExecutor = executor;
+        // Typically, the context is the Service context of an accessibility service.
+        // Context is used for ResolveInfo check, which a proxy won't have, IME input
+        // (FLAG_INPUT_METHOD_EDITOR), which the proxy doesn't need, and tracing
+        // A11yInteractionClient methods.
+        // TODO(254097475): Enable tracing, potentially without exposing Context.
+        mServiceClient = new IAccessibilityServiceClientImpl(null, mExecutor);
+        mInstalledAndEnabledServices = installedAndEnabledServices;
+    }
+
+    /**
+     * Returns the id of the display being proxy-ed.
+     */
+    public int getDisplayId() {
+        return mDisplayId;
+    }
+
+    /**
+     * An IAccessibilityServiceClient that handles interrupts and accessibility events.
+     */
+    private class IAccessibilityServiceClientImpl extends
+            AccessibilityService.IAccessibilityServiceClientWrapper {
+
+        IAccessibilityServiceClientImpl(Context context, Executor executor) {
+            super(context, executor, new AccessibilityService.Callbacks() {
+                @Override
+                public void onAccessibilityEvent(AccessibilityEvent event) {
+                    // TODO: call AccessiiblityProxy.onAccessibilityEvent
+                }
+
+                @Override
+                public void onInterrupt() {
+                    // TODO: call AccessiiblityProxy.onInterrupt
+                }
+                @Override
+                public void onServiceConnected() {
+                    // TODO: send service infos and call AccessiiblityProxy.onProxyConnected
+                }
+                @Override
+                public void init(int connectionId, IBinder windowToken) {
+                    mConnectionId = connectionId;
+                }
+
+                @Override
+                public boolean onGesture(AccessibilityGestureEvent gestureInfo) {
+                    return false;
+                }
+
+                @Override
+                public boolean onKeyEvent(KeyEvent event) {
+                    return false;
+                }
+
+                @Override
+                public void onMagnificationChanged(int displayId, @NonNull Region region,
+                        MagnificationConfig config) {
+                }
+
+                @Override
+                public void onMotionEvent(MotionEvent event) {
+                }
+
+                @Override
+                public void onTouchStateChanged(int displayId, int state) {
+                }
+
+                @Override
+                public void onSoftKeyboardShowModeChanged(int showMode) {
+                }
+
+                @Override
+                public void onPerformGestureResult(int sequence, boolean completedSuccessfully) {
+                }
+
+                @Override
+                public void onFingerprintCapturingGesturesChanged(boolean active) {
+                }
+
+                @Override
+                public void onFingerprintGesture(int gesture) {
+                }
+
+                @Override
+                public void onAccessibilityButtonClicked(int displayId) {
+                }
+
+                @Override
+                public void onAccessibilityButtonAvailabilityChanged(boolean available) {
+                }
+
+                @Override
+                public void onSystemActionsChanged() {
+                }
+
+                @Override
+                public void createImeSession(IAccessibilityInputMethodSessionCallback callback) {
+                }
+
+                @Override
+                public void startInput(@Nullable RemoteAccessibilityInputConnection inputConnection,
+                        @NonNull EditorInfo editorInfo, boolean restarting) {
+                }
+            });
+        }
+    }
+}
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index 5433fa0..423c560 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -1921,6 +1921,67 @@
         }
     }
 
+    /**
+     * Registers an {@link AccessibilityDisplayProxy}, so this proxy can access UI content specific
+     * to its display.
+     *
+     * @param proxy the {@link AccessibilityDisplayProxy} to register.
+     * @return {@code true} if the proxy is successfully registered.
+     *
+     * @throws IllegalArgumentException if the proxy's display is not currently tracked by a11y, is
+     * {@link android.view.Display#DEFAULT_DISPLAY}, is or lower than
+     * {@link android.view.Display#INVALID_DISPLAY}, or is already being proxy-ed.
+     *
+     * @throws SecurityException if the app does not hold the
+     * {@link Manifest.permission#MANAGE_ACCESSIBILITY} permission.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
+    public boolean registerDisplayProxy(@NonNull AccessibilityDisplayProxy proxy) {
+        final IAccessibilityManager service;
+        synchronized (mLock) {
+            service = getServiceLocked();
+            if (service == null) {
+                return false;
+            }
+        }
+
+        try {
+            return service.registerProxyForDisplay(proxy.mServiceClient, proxy.getDisplayId());
+        }  catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Unregisters an {@link AccessibilityDisplayProxy}.
+     *
+     * @return {@code true} if the proxy is successfully unregistered.
+     *
+     * @throws SecurityException if the app does not hold the
+     * {@link Manifest.permission#MANAGE_ACCESSIBILITY} permission.
+     *
+     * @hide
+     */
+    @SystemApi
+    @RequiresPermission(Manifest.permission.MANAGE_ACCESSIBILITY)
+    public boolean unregisterDisplayProxy(@NonNull AccessibilityDisplayProxy proxy)  {
+        final IAccessibilityManager service;
+        synchronized (mLock) {
+            service = getServiceLocked();
+            if (service == null) {
+                return false;
+            }
+        }
+        try {
+            return service.unregisterProxyForDisplay(proxy.getDisplayId());
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
     private IAccessibilityManager getServiceLocked() {
         if (mService == null) {
             tryConnectToServiceLocked(null);
diff --git a/core/java/android/view/accessibility/IAccessibilityManager.aidl b/core/java/android/view/accessibility/IAccessibilityManager.aidl
index 36fdcce4..a251948 100644
--- a/core/java/android/view/accessibility/IAccessibilityManager.aidl
+++ b/core/java/android/view/accessibility/IAccessibilityManager.aidl
@@ -109,9 +109,9 @@
 
     oneway void setAccessibilityWindowAttributes(int displayId, int windowId, int userId, in AccessibilityWindowAttributes attributes);
 
-    // Requires Manifest.permission.MANAGE_ACCESSIBILITY
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY)")
     boolean registerProxyForDisplay(IAccessibilityServiceClient proxy, int displayId);
 
-    // Requires Manifest.permission.MANAGE_ACCESSIBILITY
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MANAGE_ACCESSIBILITY)")
     boolean unregisterProxyForDisplay(int displayId);
 }
diff --git a/core/java/android/view/autofill/AutofillManager.java b/core/java/android/view/autofill/AutofillManager.java
index 70cfc3e..ef683b7 100644
--- a/core/java/android/view/autofill/AutofillManager.java
+++ b/core/java/android/view/autofill/AutofillManager.java
@@ -19,6 +19,7 @@
 import static android.service.autofill.FillRequest.FLAG_IME_SHOWING;
 import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
 import static android.service.autofill.FillRequest.FLAG_PASSWORD_INPUT_TYPE;
+import static android.service.autofill.FillRequest.FLAG_RESET_FILL_DIALOG_STATE;
 import static android.service.autofill.FillRequest.FLAG_SUPPORTS_FILL_DIALOG;
 import static android.service.autofill.FillRequest.FLAG_VIEW_NOT_FOCUSED;
 import static android.view.ContentInfo.SOURCE_AUTOFILL;
@@ -734,7 +735,7 @@
      * Autofill will automatically trigger a fill request after activity
      * start if there is any field is autofillable. But if there is a field that
      * triggered autofill, it is unnecessary to trigger again through
-     * AutofillManager#notifyViewEnteredForActivityStarted.
+     * AutofillManager#notifyViewEnteredForFillDialog.
      */
     private AtomicBoolean mIsFillRequested;
 
@@ -747,6 +748,10 @@
 
     private final String[] mFillDialogEnabledHints;
 
+    // Tracked all views that have appeared, including views that there are no
+    // dataset in responses. Used to avoid request pre-fill request again and again.
+    private final ArraySet<AutofillId> mAllTrackedViews = new ArraySet<>();
+
     /** @hide */
     public interface AutofillClient {
         /**
@@ -1192,6 +1197,16 @@
      * @hide
      */
     public void notifyViewEnteredForFillDialog(View v) {
+        synchronized (mLock) {
+            if (mTrackedViews != null) {
+                // To support the fill dialog can show for the autofillable Views in
+                // different pages but in the same Activity. We need to reset the
+                // mIsFillRequested flag to allow asking for a new FillRequest when
+                // user switches to other page
+                mTrackedViews.checkViewState(v.getAutofillId());
+            }
+        }
+
         // Skip if the fill request has been performed for a view.
         if (mIsFillRequested.get()) {
             return;
@@ -1318,6 +1333,10 @@
                         }
                         mForAugmentedAutofillOnly = false;
                     }
+
+                    if ((flags & FLAG_SUPPORTS_FILL_DIALOG) != 0) {
+                        flags |= FLAG_RESET_FILL_DIALOG_STATE;
+                    }
                     updateSessionLocked(id, null, value, ACTION_VIEW_ENTERED, flags);
                 }
                 addEnteredIdLocked(id);
@@ -2217,6 +2236,7 @@
         mIsFillRequested.set(false);
         mShowAutofillDialogCalled = false;
         mFillDialogTriggerIds = null;
+        mAllTrackedViews.clear();
         if (resetEnteredIds) {
             mEnteredIds = null;
         }
@@ -2776,14 +2796,9 @@
                         + ", mFillableIds=" + mFillableIds
                         + ", mEnabled=" + mEnabled
                         + ", mSessionId=" + mSessionId);
-
             }
+
             if (mEnabled && mSessionId == sessionId) {
-                if (saveOnAllViewsInvisible) {
-                    mTrackedViews = new TrackedViews(trackedIds);
-                } else {
-                    mTrackedViews = null;
-                }
                 mSaveOnFinish = saveOnFinish;
                 if (fillableIds != null) {
                     if (mFillableIds == null) {
@@ -2805,6 +2820,27 @@
                     mSaveTriggerId = saveTriggerId;
                     setNotifyOnClickLocked(mSaveTriggerId, true);
                 }
+
+                if (!saveOnAllViewsInvisible) {
+                    trackedIds = null;
+                }
+
+                final ArraySet<AutofillId> allFillableIds = new ArraySet<>();
+                if (mFillableIds != null) {
+                    allFillableIds.addAll(mFillableIds);
+                }
+                if (trackedIds != null) {
+                    for (AutofillId id : trackedIds) {
+                        id.resetSessionId();
+                        allFillableIds.add(id);
+                    }
+                }
+
+                if (!allFillableIds.isEmpty()) {
+                    mTrackedViews = new TrackedViews(trackedIds, Helper.toArray(allFillableIds));
+                } else {
+                    mTrackedViews = null;
+                }
             }
         }
     }
@@ -3576,10 +3612,19 @@
      */
     private class TrackedViews {
         /** Visible tracked views */
-        @Nullable private ArraySet<AutofillId> mVisibleTrackedIds;
+        @NonNull private final ArraySet<AutofillId> mVisibleTrackedIds;
 
         /** Invisible tracked views */
-        @Nullable private ArraySet<AutofillId> mInvisibleTrackedIds;
+        @NonNull private final ArraySet<AutofillId> mInvisibleTrackedIds;
+
+        /** Visible tracked views for fill dialog */
+        @NonNull private final ArraySet<AutofillId> mVisibleDialogTrackedIds;
+
+        /** Invisible tracked views for fill dialog */
+        @NonNull private final ArraySet<AutofillId> mInvisibleDialogTrackedIds;
+
+        boolean mHasNewTrackedView;
+        boolean mIsTrackedSaveView;
 
         /**
          * Check if set is null or value is in set.
@@ -3645,43 +3690,65 @@
          *
          * @param trackedIds The views to be tracked
          */
-        TrackedViews(@Nullable AutofillId[] trackedIds) {
-            final AutofillClient client = getClient();
-            if (!ArrayUtils.isEmpty(trackedIds) && client != null) {
-                final boolean[] isVisible;
+        TrackedViews(@Nullable AutofillId[] trackedIds, @Nullable AutofillId[] allTrackedIds) {
+            mVisibleTrackedIds = new ArraySet<>();
+            mInvisibleTrackedIds = new ArraySet<>();
+            if (!ArrayUtils.isEmpty(trackedIds)) {
+                mIsTrackedSaveView = true;
+                initialTrackedViews(trackedIds, mVisibleTrackedIds, mInvisibleTrackedIds);
+            }
 
-                if (client.autofillClientIsVisibleForAutofill()) {
-                    if (sVerbose) Log.v(TAG, "client is visible, check tracked ids");
-                    isVisible = client.autofillClientGetViewVisibility(trackedIds);
-                } else {
-                    // All false
-                    isVisible = new boolean[trackedIds.length];
-                }
-
-                final int numIds = trackedIds.length;
-                for (int i = 0; i < numIds; i++) {
-                    final AutofillId id = trackedIds[i];
-                    id.resetSessionId();
-
-                    if (isVisible[i]) {
-                        mVisibleTrackedIds = addToSet(mVisibleTrackedIds, id);
-                    } else {
-                        mInvisibleTrackedIds = addToSet(mInvisibleTrackedIds, id);
-                    }
-                }
+            mVisibleDialogTrackedIds = new ArraySet<>();
+            mInvisibleDialogTrackedIds = new ArraySet<>();
+            if (!ArrayUtils.isEmpty(allTrackedIds)) {
+                initialTrackedViews(allTrackedIds, mVisibleDialogTrackedIds,
+                        mInvisibleDialogTrackedIds);
+                mAllTrackedViews.addAll(Arrays.asList(allTrackedIds));
             }
 
             if (sVerbose) {
                 Log.v(TAG, "TrackedViews(trackedIds=" + Arrays.toString(trackedIds) + "): "
                         + " mVisibleTrackedIds=" + mVisibleTrackedIds
-                        + " mInvisibleTrackedIds=" + mInvisibleTrackedIds);
+                        + " mInvisibleTrackedIds=" + mInvisibleTrackedIds
+                        + " allTrackedIds=" + Arrays.toString(allTrackedIds)
+                        + " mVisibleDialogTrackedIds=" + mVisibleDialogTrackedIds
+                        + " mInvisibleDialogTrackedIds=" + mInvisibleDialogTrackedIds);
             }
 
-            if (mVisibleTrackedIds == null) {
+            if (mIsTrackedSaveView && mVisibleTrackedIds.isEmpty()) {
                 finishSessionLocked(/* commitReason= */ COMMIT_REASON_VIEW_CHANGED);
             }
         }
 
+        private void initialTrackedViews(AutofillId[] trackedIds,
+                @NonNull ArraySet<AutofillId> visibleSet,
+                @NonNull ArraySet<AutofillId> invisibleSet) {
+            final boolean[] isVisible;
+            final AutofillClient client = getClient();
+            if (ArrayUtils.isEmpty(trackedIds) || client == null) {
+                return;
+            }
+            if (client.autofillClientIsVisibleForAutofill()) {
+                if (sVerbose) Log.v(TAG, "client is visible, check tracked ids");
+                isVisible = client.autofillClientGetViewVisibility(trackedIds);
+            } else {
+                // All false
+                isVisible = new boolean[trackedIds.length];
+            }
+
+            final int numIds = trackedIds.length;
+            for (int i = 0; i < numIds; i++) {
+                final AutofillId id = trackedIds[i];
+                id.resetSessionId();
+
+                if (isVisible[i]) {
+                    addToSet(visibleSet, id);
+                } else {
+                    addToSet(invisibleSet, id);
+                }
+            }
+        }
+
         /**
          * Called when a {@link View view's} visibility changes.
          *
@@ -3698,22 +3765,37 @@
             if (isClientVisibleForAutofillLocked()) {
                 if (isVisible) {
                     if (isInSet(mInvisibleTrackedIds, id)) {
-                        mInvisibleTrackedIds = removeFromSet(mInvisibleTrackedIds, id);
-                        mVisibleTrackedIds = addToSet(mVisibleTrackedIds, id);
+                        removeFromSet(mInvisibleTrackedIds, id);
+                        addToSet(mVisibleTrackedIds, id);
+                    }
+                    if (isInSet(mInvisibleDialogTrackedIds, id)) {
+                        removeFromSet(mInvisibleDialogTrackedIds, id);
+                        addToSet(mVisibleDialogTrackedIds, id);
                     }
                 } else {
                     if (isInSet(mVisibleTrackedIds, id)) {
-                        mVisibleTrackedIds = removeFromSet(mVisibleTrackedIds, id);
-                        mInvisibleTrackedIds = addToSet(mInvisibleTrackedIds, id);
+                        removeFromSet(mVisibleTrackedIds, id);
+                        addToSet(mInvisibleTrackedIds, id);
+                    }
+                    if (isInSet(mVisibleDialogTrackedIds, id)) {
+                        removeFromSet(mVisibleDialogTrackedIds, id);
+                        addToSet(mInvisibleDialogTrackedIds, id);
                     }
                 }
             }
 
-            if (mVisibleTrackedIds == null) {
+            if (mIsTrackedSaveView && mVisibleTrackedIds.isEmpty()) {
                 if (sVerbose) {
                     Log.v(TAG, "No more visible ids. Invisible = " + mInvisibleTrackedIds);
                 }
                 finishSessionLocked(/* commitReason= */ COMMIT_REASON_VIEW_CHANGED);
+
+            }
+            if (mVisibleDialogTrackedIds.isEmpty()) {
+                if (sVerbose) {
+                    Log.v(TAG, "No more visible ids. Invisible = " + mInvisibleDialogTrackedIds);
+                }
+                processNoVisibleTrackedAllViews();
             }
         }
 
@@ -3727,66 +3809,66 @@
             // The visibility of the views might have changed while the client was not be visible,
             // hence update the visibility state for all views.
             AutofillClient client = getClient();
-            ArraySet<AutofillId> updatedVisibleTrackedIds = null;
-            ArraySet<AutofillId> updatedInvisibleTrackedIds = null;
             if (client != null) {
                 if (sVerbose) {
                     Log.v(TAG, "onVisibleForAutofillChangedLocked(): inv= " + mInvisibleTrackedIds
                             + " vis=" + mVisibleTrackedIds);
                 }
-                if (mInvisibleTrackedIds != null) {
-                    final ArrayList<AutofillId> orderedInvisibleIds =
-                            new ArrayList<>(mInvisibleTrackedIds);
-                    final boolean[] isVisible = client.autofillClientGetViewVisibility(
-                            Helper.toArray(orderedInvisibleIds));
 
-                    final int numInvisibleTrackedIds = orderedInvisibleIds.size();
-                    for (int i = 0; i < numInvisibleTrackedIds; i++) {
-                        final AutofillId id = orderedInvisibleIds.get(i);
-                        if (isVisible[i]) {
-                            updatedVisibleTrackedIds = addToSet(updatedVisibleTrackedIds, id);
-
-                            if (sDebug) {
-                                Log.d(TAG, "onVisibleForAutofill() " + id + " became visible");
-                            }
-                        } else {
-                            updatedInvisibleTrackedIds = addToSet(updatedInvisibleTrackedIds, id);
-                        }
-                    }
-                }
-
-                if (mVisibleTrackedIds != null) {
-                    final ArrayList<AutofillId> orderedVisibleIds =
-                            new ArrayList<>(mVisibleTrackedIds);
-                    final boolean[] isVisible = client.autofillClientGetViewVisibility(
-                            Helper.toArray(orderedVisibleIds));
-
-                    final int numVisibleTrackedIds = orderedVisibleIds.size();
-                    for (int i = 0; i < numVisibleTrackedIds; i++) {
-                        final AutofillId id = orderedVisibleIds.get(i);
-
-                        if (isVisible[i]) {
-                            updatedVisibleTrackedIds = addToSet(updatedVisibleTrackedIds, id);
-                        } else {
-                            updatedInvisibleTrackedIds = addToSet(updatedInvisibleTrackedIds, id);
-
-                            if (sDebug) {
-                                Log.d(TAG, "onVisibleForAutofill() " + id + " became invisible");
-                            }
-                        }
-                    }
-                }
-
-                mInvisibleTrackedIds = updatedInvisibleTrackedIds;
-                mVisibleTrackedIds = updatedVisibleTrackedIds;
+                onVisibleForAutofillChangedInternalLocked(mVisibleTrackedIds, mInvisibleTrackedIds);
+                onVisibleForAutofillChangedInternalLocked(
+                        mVisibleDialogTrackedIds, mInvisibleDialogTrackedIds);
             }
 
-            if (mVisibleTrackedIds == null) {
+            if (mIsTrackedSaveView && mVisibleTrackedIds.isEmpty()) {
                 if (sVerbose) {
-                    Log.v(TAG, "onVisibleForAutofillChangedLocked(): no more visible ids");
+                    Log.v(TAG,  "onVisibleForAutofillChangedLocked(): no more visible ids");
                 }
                 finishSessionLocked(/* commitReason= */ COMMIT_REASON_VIEW_CHANGED);
             }
+            if (mVisibleDialogTrackedIds.isEmpty()) {
+                if (sVerbose) {
+                    Log.v(TAG,  "onVisibleForAutofillChangedLocked(): no more visible ids");
+                }
+                processNoVisibleTrackedAllViews();
+            }
+        }
+
+        void onVisibleForAutofillChangedInternalLocked(@NonNull ArraySet<AutofillId> visibleSet,
+                @NonNull ArraySet<AutofillId> invisibleSet) {
+            // The visibility of the views might have changed while the client was not be visible,
+            // hence update the visibility state for all views.
+            if (sVerbose) {
+                Log.v(TAG, "onVisibleForAutofillChangedLocked(): inv= " + invisibleSet
+                        + " vis=" + visibleSet);
+            }
+
+            ArraySet<AutofillId> allTrackedIds = new ArraySet<>();
+            allTrackedIds.addAll(visibleSet);
+            allTrackedIds.addAll(invisibleSet);
+            if (!allTrackedIds.isEmpty()) {
+                visibleSet.clear();
+                invisibleSet.clear();
+                initialTrackedViews(Helper.toArray(allTrackedIds), visibleSet, invisibleSet);
+            }
+        }
+
+        private void processNoVisibleTrackedAllViews() {
+            mShowAutofillDialogCalled = false;
+        }
+
+        void checkViewState(AutofillId id) {
+            if (mAllTrackedViews.contains(id)) {
+                return;
+            }
+            // Add the id as tracked to avoid triggering fill request again and again.
+            mAllTrackedViews.add(id);
+            if (mHasNewTrackedView) {
+                return;
+            }
+            // First one new tracks view
+            mIsFillRequested.set(false);
+            mHasNewTrackedView = true;
         }
     }
 
diff --git a/core/java/android/view/contentcapture/ContentCaptureManager.java b/core/java/android/view/contentcapture/ContentCaptureManager.java
index 1664637..d067d4b 100644
--- a/core/java/android/view/contentcapture/ContentCaptureManager.java
+++ b/core/java/android/view/contentcapture/ContentCaptureManager.java
@@ -378,7 +378,7 @@
     private final Object mLock = new Object();
 
     @NonNull
-    private final Context mContext;
+    private final StrippedContext mContext;
 
     @NonNull
     private final IContentCaptureManager mService;
@@ -414,9 +414,37 @@
     }
 
     /** @hide */
+    static class StrippedContext {
+        final String mPackageName;
+        final String mContext;
+        final @UserIdInt int mUserId;
+
+        private StrippedContext(Context context) {
+            mPackageName = context.getPackageName();
+            mContext = context.toString();
+            mUserId = context.getUserId();
+        }
+
+        @Override
+        public String toString() {
+            return mContext;
+        }
+
+        public String getPackageName() {
+            return mPackageName;
+        }
+
+        @UserIdInt
+        public int getUserId() {
+            return mUserId;
+        }
+    }
+
+    /** @hide */
     public ContentCaptureManager(@NonNull Context context,
             @NonNull IContentCaptureManager service, @NonNull ContentCaptureOptions options) {
-        mContext = Objects.requireNonNull(context, "context cannot be null");
+        Objects.requireNonNull(context, "context cannot be null");
+        mContext = new StrippedContext(context);
         mService = Objects.requireNonNull(service, "service cannot be null");
         mOptions = Objects.requireNonNull(options, "options cannot be null");
 
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index c32ca9e..a989558 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -36,7 +36,6 @@
 import android.annotation.Nullable;
 import android.annotation.UiThread;
 import android.content.ComponentName;
-import android.content.Context;
 import android.content.pm.ParceledListSlice;
 import android.graphics.Insets;
 import android.graphics.Rect;
@@ -103,7 +102,7 @@
     private final AtomicBoolean mDisabled = new AtomicBoolean(false);
 
     @NonNull
-    private final Context mContext;
+    private final ContentCaptureManager.StrippedContext mContext;
 
     @NonNull
     private final ContentCaptureManager mManager;
@@ -197,7 +196,7 @@
         }
     }
 
-    protected MainContentCaptureSession(@NonNull Context context,
+    protected MainContentCaptureSession(@NonNull ContentCaptureManager.StrippedContext context,
             @NonNull ContentCaptureManager manager, @NonNull Handler handler,
             @NonNull IContentCaptureManager systemServerInterface) {
         mContext = context;
diff --git a/core/java/android/view/inputmethod/CursorAnchorInfo.java b/core/java/android/view/inputmethod/CursorAnchorInfo.java
index a8ed96e..2d974db 100644
--- a/core/java/android/view/inputmethod/CursorAnchorInfo.java
+++ b/core/java/android/view/inputmethod/CursorAnchorInfo.java
@@ -20,12 +20,14 @@
 import android.annotation.Nullable;
 import android.graphics.Matrix;
 import android.graphics.RectF;
+import android.inputmethodservice.InputMethodService;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.Layout;
 import android.text.SpannedString;
 import android.text.TextUtils;
 import android.view.inputmethod.SparseRectFArray.SparseRectFArrayBuilder;
+import android.widget.TextView;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -110,7 +112,7 @@
     /**
      * Container of rectangular position of Editor in the local coordinates that will be transformed
      * with the transformation matrix when rendered on the screen.
-     * @see {@link EditorBoundsInfo}.
+     * @see EditorBoundsInfo
      */
     private final EditorBoundsInfo mEditorBoundsInfo;
 
@@ -122,6 +124,12 @@
     private final float[] mMatrixValues;
 
     /**
+     * Information about text appearance in the editor for use by {@link InputMethodService}.
+     */
+    @Nullable
+    private final TextAppearanceInfo mTextAppearanceInfo;
+
+    /**
      * A list of visible line bounds stored in a float array. This array is divided into segment of
      * four where each element in the segment represents left, top, right respectively and bottom
      * of the line bounds.
@@ -157,10 +165,11 @@
         mInsertionMarkerTop = source.readFloat();
         mInsertionMarkerBaseline = source.readFloat();
         mInsertionMarkerBottom = source.readFloat();
-        mCharacterBoundsArray = source.readParcelable(SparseRectFArray.class.getClassLoader(), android.view.inputmethod.SparseRectFArray.class);
+        mCharacterBoundsArray = source.readTypedObject(SparseRectFArray.CREATOR);
         mEditorBoundsInfo = source.readTypedObject(EditorBoundsInfo.CREATOR);
         mMatrixValues = source.createFloatArray();
         mVisibleLineBounds = source.createFloatArray();
+        mTextAppearanceInfo = source.readTypedObject(TextAppearanceInfo.CREATOR);
     }
 
     /**
@@ -181,10 +190,11 @@
         dest.writeFloat(mInsertionMarkerTop);
         dest.writeFloat(mInsertionMarkerBaseline);
         dest.writeFloat(mInsertionMarkerBottom);
-        dest.writeParcelable(mCharacterBoundsArray, flags);
+        dest.writeTypedObject(mCharacterBoundsArray, flags);
         dest.writeTypedObject(mEditorBoundsInfo, flags);
         dest.writeFloatArray(mMatrixValues);
         dest.writeFloatArray(mVisibleLineBounds);
+        dest.writeTypedObject(mTextAppearanceInfo, flags);
     }
 
     @Override
@@ -262,6 +272,11 @@
                 return false;
             }
         }
+
+        if (!Objects.equals(mTextAppearanceInfo, that.mTextAppearanceInfo)) {
+            return false;
+        }
+
         return true;
     }
 
@@ -270,16 +285,17 @@
         return "CursorAnchorInfo{mHashCode=" + mHashCode
                 + " mSelection=" + mSelectionStart + "," + mSelectionEnd
                 + " mComposingTextStart=" + mComposingTextStart
-                + " mComposingText=" + Objects.toString(mComposingText)
+                + " mComposingText=" + mComposingText
                 + " mInsertionMarkerFlags=" + mInsertionMarkerFlags
                 + " mInsertionMarkerHorizontal=" + mInsertionMarkerHorizontal
                 + " mInsertionMarkerTop=" + mInsertionMarkerTop
                 + " mInsertionMarkerBaseline=" + mInsertionMarkerBaseline
                 + " mInsertionMarkerBottom=" + mInsertionMarkerBottom
-                + " mCharacterBoundsArray=" + Objects.toString(mCharacterBoundsArray)
+                + " mCharacterBoundsArray=" + mCharacterBoundsArray
                 + " mEditorBoundsInfo=" + mEditorBoundsInfo
                 + " mVisibleLineBounds=" + getVisibleLineBounds()
                 + " mMatrix=" + Arrays.toString(mMatrixValues)
+                + " mTextAppearanceInfo=" + mTextAppearanceInfo
                 + "}";
     }
 
@@ -303,6 +319,7 @@
         private boolean mMatrixInitialized = false;
         private float[] mVisibleLineBounds = new float[LINE_BOUNDS_INITIAL_SIZE * 4];
         private int mVisibleLineBoundsCount = 0;
+        private TextAppearanceInfo mTextAppearanceInfo = null;
 
         /**
          * Sets the text range of the selection. Calling this can be skipped if there is no
@@ -416,6 +433,17 @@
         }
 
         /**
+         * Set the information related to text appearance, which is extracted from the original
+         * {@link TextView}.
+         * @param textAppearanceInfo {@link TextAppearanceInfo} of TextView.
+         */
+        @NonNull
+        public Builder setTextAppearanceInfo(@Nullable TextAppearanceInfo textAppearanceInfo) {
+            mTextAppearanceInfo = textAppearanceInfo;
+            return this;
+        }
+
+        /**
          * Add the bounds of a visible text line of the current editor.
          *
          * The line bounds should not include the vertical space between lines or the horizontal
@@ -504,6 +532,7 @@
             }
             mEditorBoundsInfo = null;
             clearVisibleLineBounds();
+            mTextAppearanceInfo = null;
         }
     }
 
@@ -524,7 +553,8 @@
                 builder.mInsertionMarkerHorizontal, builder.mInsertionMarkerTop,
                 builder.mInsertionMarkerBaseline, builder.mInsertionMarkerBottom,
                 characterBoundsArray, builder.mEditorBoundsInfo, matrixValues,
-                Arrays.copyOf(builder.mVisibleLineBounds, builder.mVisibleLineBoundsCount));
+                Arrays.copyOf(builder.mVisibleLineBounds, builder.mVisibleLineBoundsCount),
+                builder.mTextAppearanceInfo);
     }
 
     private CursorAnchorInfo(int selectionStart, int selectionEnd, int composingTextStart,
@@ -533,7 +563,8 @@
             float insertionMarkerBaseline, float insertionMarkerBottom,
             @Nullable SparseRectFArray characterBoundsArray,
             @Nullable EditorBoundsInfo editorBoundsInfo,
-            @NonNull float[] matrixValues, @Nullable float[] visibleLineBounds) {
+            @NonNull float[] matrixValues, @Nullable float[] visibleLineBounds,
+            @Nullable TextAppearanceInfo textAppearanceInfo) {
         mSelectionStart = selectionStart;
         mSelectionEnd = selectionEnd;
         mComposingTextStart = composingTextStart;
@@ -547,6 +578,7 @@
         mEditorBoundsInfo = editorBoundsInfo;
         mMatrixValues = matrixValues;
         mVisibleLineBounds = visibleLineBounds;
+        mTextAppearanceInfo = textAppearanceInfo;
 
         // To keep hash function simple, we only use some complex objects for hash.
         int hashCode = Objects.hashCode(mComposingText);
@@ -573,7 +605,7 @@
                 original.mInsertionMarkerTop, original.mInsertionMarkerBaseline,
                 original.mInsertionMarkerBottom, original.mCharacterBoundsArray,
                 original.mEditorBoundsInfo, computeMatrixValues(parentMatrix, original),
-                original.mVisibleLineBounds);
+                original.mVisibleLineBounds, original.mTextAppearanceInfo);
     }
 
     /**
@@ -741,6 +773,16 @@
     }
 
     /**
+     * Returns {@link TextAppearanceInfo} for the current editor, or {@code null} if IME is not
+     * subscribed with {@link InputConnection#CURSOR_UPDATE_FILTER_TEXT_APPEARANCE}
+     * or {@link InputConnection#CURSOR_UPDATE_MONITOR}.
+     */
+    @Nullable
+    public TextAppearanceInfo getTextAppearanceInfo() {
+        return mTextAppearanceInfo;
+    }
+
+    /**
      * Returns a new instance of {@link android.graphics.Matrix} that indicates the transformation
      * matrix that is to be applied other positional data in this class.
      * @return a new instance (copy) of the transformation matrix.
diff --git a/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
new file mode 100644
index 0000000..a66c67b
--- /dev/null
+++ b/core/java/android/view/inputmethod/IInputMethodManagerGlobalInvoker.java
@@ -0,0 +1,528 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod;
+
+import android.Manifest;
+import android.annotation.AnyThread;
+import android.annotation.DurationMillisLong;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.RequiresNoPermission;
+import android.annotation.RequiresPermission;
+import android.annotation.UserIdInt;
+import android.content.Context;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ResultReceiver;
+import android.os.ServiceManager;
+import android.view.WindowManager;
+import android.window.ImeOnBackInvokedDispatcher;
+
+import com.android.internal.inputmethod.DirectBootAwareness;
+import com.android.internal.inputmethod.IInputMethodClient;
+import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
+import com.android.internal.inputmethod.IRemoteInputConnection;
+import com.android.internal.inputmethod.InputBindResult;
+import com.android.internal.inputmethod.SoftInputShowHideReason;
+import com.android.internal.inputmethod.StartInputFlags;
+import com.android.internal.inputmethod.StartInputReason;
+import com.android.internal.view.IInputMethodManager;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * A global wrapper to directly invoke {@link IInputMethodManager} IPCs.
+ *
+ * <p>All public static methods are guaranteed to be thread-safe.</p>
+ *
+ * <p>All public methods are guaranteed to do nothing when {@link IInputMethodManager} is
+ * unavailable.</p>
+ *
+ * <p>If you want to use any of this method outside of {@code android.view.inputmethod}, create
+ * a wrapper method in {@link InputMethodManagerGlobal} instead of making this class public.</p>
+ */
+final class IInputMethodManagerGlobalInvoker {
+    @Nullable
+    private static volatile IInputMethodManager sServiceCache = null;
+
+    /**
+     * @return {@code true} if {@link IInputMethodManager} is available.
+     */
+    @AnyThread
+    static boolean isAvailable() {
+        return getService() != null;
+    }
+
+    @AnyThread
+    @Nullable
+    static IInputMethodManager getService() {
+        IInputMethodManager service = sServiceCache;
+        if (service == null) {
+            if (InputMethodManager.isInEditModeInternal()) {
+                return null;
+            }
+            service = IInputMethodManager.Stub.asInterface(
+                    ServiceManager.getService(Context.INPUT_METHOD_SERVICE));
+            if (service == null) {
+                return null;
+            }
+            sServiceCache = service;
+        }
+        return service;
+    }
+
+    @AnyThread
+    private static void handleRemoteExceptionOrRethrow(@NonNull RemoteException e,
+            @Nullable Consumer<RemoteException> exceptionHandler) {
+        if (exceptionHandler != null) {
+            exceptionHandler.accept(e);
+        } else {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Invokes {@link IInputMethodManager#startProtoDump(byte[], int, String)}.
+     *
+     * @param protoDump client or service side information to be stored by the server
+     * @param source where the information is coming from, refer to
+     *               {@link com.android.internal.inputmethod.ImeTracing#IME_TRACING_FROM_CLIENT} and
+     *               {@link com.android.internal.inputmethod.ImeTracing#IME_TRACING_FROM_IMS}
+     * @param where where the information is coming from.
+     * @param exceptionHandler an optional {@link RemoteException} handler.
+     */
+    @AnyThread
+    @RequiresNoPermission
+    static void startProtoDump(byte[] protoDump, int source, String where,
+            @Nullable Consumer<RemoteException> exceptionHandler) {
+        final IInputMethodManager service = getService();
+        if (service == null) {
+            return;
+        }
+        try {
+            service.startProtoDump(protoDump, source, where);
+        } catch (RemoteException e) {
+            handleRemoteExceptionOrRethrow(e, exceptionHandler);
+        }
+    }
+
+    /**
+     * Invokes {@link IInputMethodManager#startImeTrace()}.
+     *
+     * @param exceptionHandler an optional {@link RemoteException} handler.
+     */
+    @AnyThread
+    @RequiresPermission(Manifest.permission.CONTROL_UI_TRACING)
+    static void startImeTrace(@Nullable Consumer<RemoteException> exceptionHandler) {
+        final IInputMethodManager service = getService();
+        if (service == null) {
+            return;
+        }
+        try {
+            service.startImeTrace();
+        } catch (RemoteException e) {
+            handleRemoteExceptionOrRethrow(e, exceptionHandler);
+        }
+    }
+
+    /**
+     * Invokes {@link IInputMethodManager#stopImeTrace()}.
+     *
+     * @param exceptionHandler an optional {@link RemoteException} handler.
+     */
+    @AnyThread
+    @RequiresPermission(Manifest.permission.CONTROL_UI_TRACING)
+    static void stopImeTrace(@Nullable Consumer<RemoteException> exceptionHandler) {
+        final IInputMethodManager service = getService();
+        if (service == null) {
+            return;
+        }
+        try {
+            service.stopImeTrace();
+        } catch (RemoteException e) {
+            handleRemoteExceptionOrRethrow(e, exceptionHandler);
+        }
+    }
+
+    /**
+     * Invokes {@link IInputMethodManager#isImeTraceEnabled()}.
+     *
+     * @return The return value of {@link IInputMethodManager#isImeTraceEnabled()}.
+     */
+    @AnyThread
+    @RequiresNoPermission
+    static boolean isImeTraceEnabled() {
+        final IInputMethodManager service = getService();
+        if (service == null) {
+            return false;
+        }
+        try {
+            return service.isImeTraceEnabled();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Invokes {@link IInputMethodManager#removeImeSurface()}
+     */
+    @AnyThread
+    @RequiresPermission(Manifest.permission.INTERNAL_SYSTEM_WINDOW)
+    static void removeImeSurface(@Nullable Consumer<RemoteException> exceptionHandler) {
+        final IInputMethodManager service = getService();
+        if (service == null) {
+            return;
+        }
+        try {
+            service.removeImeSurface();
+        } catch (RemoteException e) {
+            handleRemoteExceptionOrRethrow(e, exceptionHandler);
+        }
+    }
+
+    @AnyThread
+    static void addClient(@NonNull IInputMethodClient client,
+            @NonNull IRemoteInputConnection fallbackInputConnection, int untrustedDisplayId) {
+        final IInputMethodManager service = getService();
+        if (service == null) {
+            return;
+        }
+        try {
+            service.addClient(client, fallbackInputConnection, untrustedDisplayId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    @NonNull
+    @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
+    static List<InputMethodInfo> getInputMethodList(@UserIdInt int userId,
+            @DirectBootAwareness int directBootAwareness) {
+        final IInputMethodManager service = getService();
+        if (service == null) {
+            return new ArrayList<>();
+        }
+        try {
+            return service.getInputMethodList(userId, directBootAwareness);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    @NonNull
+    @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
+    static List<InputMethodInfo> getEnabledInputMethodList(@UserIdInt int userId) {
+        final IInputMethodManager service = getService();
+        if (service == null) {
+            return new ArrayList<>();
+        }
+        try {
+            return service.getEnabledInputMethodList(userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    @NonNull
+    @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
+    static List<InputMethodSubtype> getEnabledInputMethodSubtypeList(@Nullable String imiId,
+            boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId) {
+        final IInputMethodManager service = getService();
+        if (service == null) {
+            return new ArrayList<>();
+        }
+        try {
+            return service.getEnabledInputMethodSubtypeList(imiId,
+                    allowsImplicitlyEnabledSubtypes, userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    @Nullable
+    @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
+    static InputMethodSubtype getLastInputMethodSubtype(@UserIdInt int userId) {
+        final IInputMethodManager service = getService();
+        if (service == null) {
+            return null;
+        }
+        try {
+            return service.getLastInputMethodSubtype(userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    static boolean showSoftInput(@NonNull IInputMethodClient client, @Nullable IBinder windowToken,
+            int flags, int lastClickToolType, @Nullable ResultReceiver resultReceiver,
+            @SoftInputShowHideReason int reason) {
+        final IInputMethodManager service = getService();
+        if (service == null) {
+            return false;
+        }
+        try {
+            return service.showSoftInput(
+                    client, windowToken, flags, lastClickToolType, resultReceiver, reason);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    static boolean hideSoftInput(@NonNull IInputMethodClient client, @Nullable IBinder windowToken,
+            int flags, @Nullable ResultReceiver resultReceiver,
+            @SoftInputShowHideReason int reason) {
+        final IInputMethodManager service = getService();
+        if (service == null) {
+            return false;
+        }
+        try {
+            return service.hideSoftInput(client, windowToken, flags, resultReceiver, reason);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    @NonNull
+    @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
+    static InputBindResult startInputOrWindowGainedFocus(@StartInputReason int startInputReason,
+            @NonNull IInputMethodClient client, @Nullable IBinder windowToken,
+            @StartInputFlags int startInputFlags,
+            @WindowManager.LayoutParams.SoftInputModeFlags int softInputMode,
+            @WindowManager.LayoutParams.Flags int windowFlags, @Nullable EditorInfo editorInfo,
+            @Nullable IRemoteInputConnection remoteInputConnection,
+            @Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
+            int unverifiedTargetSdkVersion, @UserIdInt int userId,
+            @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
+        final IInputMethodManager service = getService();
+        if (service == null) {
+            return InputBindResult.NULL;
+        }
+        try {
+            return service.startInputOrWindowGainedFocus(startInputReason, client, windowToken,
+                    startInputFlags, softInputMode, windowFlags, editorInfo, remoteInputConnection,
+                    remoteAccessibilityInputConnection, unverifiedTargetSdkVersion, userId,
+                    imeDispatcher);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    static void showInputMethodPickerFromClient(@NonNull IInputMethodClient client,
+            int auxiliarySubtypeMode) {
+        final IInputMethodManager service = getService();
+        if (service == null) {
+            return;
+        }
+        try {
+            service.showInputMethodPickerFromClient(client, auxiliarySubtypeMode);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
+    static void showInputMethodPickerFromSystem(int auxiliarySubtypeMode, int displayId) {
+        final IInputMethodManager service = getService();
+        if (service == null) {
+            return;
+        }
+        try {
+            service.showInputMethodPickerFromSystem(auxiliarySubtypeMode, displayId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD)
+    static boolean isInputMethodPickerShownForTest() {
+        final IInputMethodManager service = getService();
+        if (service == null) {
+            return false;
+        }
+        try {
+            return service.isInputMethodPickerShownForTest();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    @Nullable
+    @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
+    static InputMethodSubtype getCurrentInputMethodSubtype(@UserIdInt int userId) {
+        final IInputMethodManager service = getService();
+        if (service == null) {
+            return null;
+        }
+        try {
+            return service.getCurrentInputMethodSubtype(userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
+    static void setAdditionalInputMethodSubtypes(@NonNull String imeId,
+            @NonNull InputMethodSubtype[] subtypes, @UserIdInt int userId) {
+        final IInputMethodManager service = getService();
+        if (service == null) {
+            return;
+        }
+        try {
+            service.setAdditionalInputMethodSubtypes(imeId, subtypes, userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
+    static void setExplicitlyEnabledInputMethodSubtypes(@NonNull String imeId,
+            @NonNull int[] subtypeHashCodes, @UserIdInt int userId) {
+        final IInputMethodManager service = getService();
+        if (service == null) {
+            return;
+        }
+        try {
+            service.setExplicitlyEnabledInputMethodSubtypes(imeId, subtypeHashCodes, userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    static int getInputMethodWindowVisibleHeight(@NonNull IInputMethodClient client) {
+        final IInputMethodManager service = getService();
+        if (service == null) {
+            return 0;
+        }
+        try {
+            return service.getInputMethodWindowVisibleHeight(client);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    static void reportVirtualDisplayGeometryAsync(@NonNull IInputMethodClient client,
+            int childDisplayId, @Nullable float[] matrixValues) {
+        final IInputMethodManager service = getService();
+        if (service == null) {
+            return;
+        }
+        try {
+            service.reportVirtualDisplayGeometryAsync(client, childDisplayId, matrixValues);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    static void reportPerceptibleAsync(@NonNull IBinder windowToken, boolean perceptible) {
+        final IInputMethodManager service = getService();
+        if (service == null) {
+            return;
+        }
+        try {
+            service.reportPerceptibleAsync(windowToken, perceptible);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    static void removeImeSurfaceFromWindowAsync(@NonNull IBinder windowToken) {
+        final IInputMethodManager service = getService();
+        if (service == null) {
+            return;
+        }
+        try {
+            service.removeImeSurfaceFromWindowAsync(windowToken);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    static void startStylusHandwriting(@NonNull IInputMethodClient client) {
+        final IInputMethodManager service = getService();
+        if (service == null) {
+            return;
+        }
+        try {
+            service.startStylusHandwriting(client);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
+    static boolean isStylusHandwritingAvailableAsUser(@UserIdInt int userId) {
+        final IInputMethodManager service = getService();
+        if (service == null) {
+            return false;
+        }
+        try {
+            return service.isStylusHandwritingAvailableAsUser(userId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD)
+    static void addVirtualStylusIdForTestSession(IInputMethodClient client) {
+        final IInputMethodManager service = getService();
+        if (service == null) {
+            return;
+        }
+        try {
+            service.addVirtualStylusIdForTestSession(client);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    @AnyThread
+    @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD)
+    static void setStylusWindowIdleTimeoutForTest(
+            IInputMethodClient client, @DurationMillisLong long timeout) {
+        final IInputMethodManager service = getService();
+        if (service == null) {
+            return;
+        }
+        try {
+            service.setStylusWindowIdleTimeoutForTest(client, timeout);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+}
diff --git a/core/java/android/view/inputmethod/IInputMethodManagerInvoker.java b/core/java/android/view/inputmethod/IInputMethodManagerInvoker.java
deleted file mode 100644
index 01e8b34..0000000
--- a/core/java/android/view/inputmethod/IInputMethodManagerInvoker.java
+++ /dev/null
@@ -1,288 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.view.inputmethod;
-
-import android.annotation.AnyThread;
-import android.annotation.DurationMillisLong;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.UserIdInt;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.os.ResultReceiver;
-import android.view.WindowManager;
-import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
-import android.window.ImeOnBackInvokedDispatcher;
-
-import com.android.internal.inputmethod.DirectBootAwareness;
-import com.android.internal.inputmethod.IInputMethodClient;
-import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
-import com.android.internal.inputmethod.IRemoteInputConnection;
-import com.android.internal.inputmethod.InputBindResult;
-import com.android.internal.inputmethod.SoftInputShowHideReason;
-import com.android.internal.inputmethod.StartInputFlags;
-import com.android.internal.inputmethod.StartInputReason;
-import com.android.internal.view.IInputMethodManager;
-
-import java.util.List;
-
-/**
- * A wrapper class to invoke IPCs defined in {@link IInputMethodManager}.
- */
-final class IInputMethodManagerInvoker {
-    @NonNull
-    private final IInputMethodManager mTarget;
-
-    private IInputMethodManagerInvoker(@NonNull IInputMethodManager target) {
-        mTarget = target;
-    }
-
-    @AnyThread
-    @NonNull
-    static IInputMethodManagerInvoker create(@NonNull IInputMethodManager imm) {
-        return new IInputMethodManagerInvoker(imm);
-    }
-
-    @AnyThread
-    void addClient(@NonNull IInputMethodClient client,
-            @NonNull IRemoteInputConnection fallbackInputConnection, int untrustedDisplayId) {
-        try {
-            mTarget.addClient(client, fallbackInputConnection, untrustedDisplayId);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    @AnyThread
-    @NonNull
-    List<InputMethodInfo> getInputMethodList(@UserIdInt int userId,
-            @DirectBootAwareness int directBootAwareness) {
-        try {
-            return mTarget.getInputMethodList(userId, directBootAwareness);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    @AnyThread
-    @NonNull
-    List<InputMethodInfo> getEnabledInputMethodList(@UserIdInt int userId) {
-        try {
-            return mTarget.getEnabledInputMethodList(userId);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    @AnyThread
-    @NonNull
-    List<InputMethodSubtype> getEnabledInputMethodSubtypeList(@Nullable String imiId,
-            boolean allowsImplicitlyEnabledSubtypes, @UserIdInt int userId) {
-        try {
-            return mTarget.getEnabledInputMethodSubtypeList(imiId,
-                    allowsImplicitlyEnabledSubtypes, userId);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    @AnyThread
-    @Nullable
-    InputMethodSubtype getLastInputMethodSubtype(@UserIdInt int userId) {
-        try {
-            return mTarget.getLastInputMethodSubtype(userId);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    @AnyThread
-    boolean showSoftInput(@NonNull IInputMethodClient client, @Nullable IBinder windowToken,
-            int flags, int lastClickToolType, @Nullable ResultReceiver resultReceiver,
-            @SoftInputShowHideReason int reason) {
-        try {
-            return mTarget.showSoftInput(
-                    client, windowToken, flags, lastClickToolType, resultReceiver, reason);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    @AnyThread
-    boolean hideSoftInput(@NonNull IInputMethodClient client, @Nullable IBinder windowToken,
-            int flags, @Nullable ResultReceiver resultReceiver,
-            @SoftInputShowHideReason int reason) {
-        try {
-            return mTarget.hideSoftInput(client, windowToken, flags, resultReceiver, reason);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    @AnyThread
-    @NonNull
-    InputBindResult startInputOrWindowGainedFocus(@StartInputReason int startInputReason,
-            @NonNull IInputMethodClient client, @Nullable IBinder windowToken,
-            @StartInputFlags int startInputFlags, @SoftInputModeFlags int softInputMode,
-            @WindowManager.LayoutParams.Flags int windowFlags, @Nullable EditorInfo editorInfo,
-            @Nullable IRemoteInputConnection remoteInputConnection,
-            @Nullable IRemoteAccessibilityInputConnection remoteAccessibilityInputConnection,
-            int unverifiedTargetSdkVersion, @UserIdInt int userId,
-            @NonNull ImeOnBackInvokedDispatcher imeDispatcher) {
-        try {
-            return mTarget.startInputOrWindowGainedFocus(startInputReason, client, windowToken,
-                    startInputFlags, softInputMode, windowFlags, editorInfo, remoteInputConnection,
-                    remoteAccessibilityInputConnection, unverifiedTargetSdkVersion, userId,
-                    imeDispatcher);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    @AnyThread
-    void showInputMethodPickerFromClient(@NonNull IInputMethodClient client,
-            int auxiliarySubtypeMode) {
-        try {
-            mTarget.showInputMethodPickerFromClient(client, auxiliarySubtypeMode);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    @AnyThread
-    void showInputMethodPickerFromSystem(@NonNull IInputMethodClient client,
-            int auxiliarySubtypeMode, int displayId) {
-        try {
-            mTarget.showInputMethodPickerFromSystem(client, auxiliarySubtypeMode, displayId);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    @AnyThread
-    boolean isInputMethodPickerShownForTest() {
-        try {
-            return mTarget.isInputMethodPickerShownForTest();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    @AnyThread
-    @Nullable
-    InputMethodSubtype getCurrentInputMethodSubtype(@UserIdInt int userId) {
-        try {
-            return mTarget.getCurrentInputMethodSubtype(userId);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    @AnyThread
-    void setAdditionalInputMethodSubtypes(@NonNull String imeId,
-            @NonNull InputMethodSubtype[] subtypes, @UserIdInt int userId) {
-        try {
-            mTarget.setAdditionalInputMethodSubtypes(imeId, subtypes, userId);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    @AnyThread
-    void setExplicitlyEnabledInputMethodSubtypes(@NonNull String imeId,
-            @NonNull int[] subtypeHashCodes, @UserIdInt int userId) {
-        try {
-            mTarget.setExplicitlyEnabledInputMethodSubtypes(imeId, subtypeHashCodes, userId);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    @AnyThread
-    int getInputMethodWindowVisibleHeight(@NonNull IInputMethodClient client) {
-        try {
-            return mTarget.getInputMethodWindowVisibleHeight(client);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    @AnyThread
-    void reportVirtualDisplayGeometryAsync(@NonNull IInputMethodClient client, int childDisplayId,
-            @Nullable float[] matrixValues) {
-        try {
-            mTarget.reportVirtualDisplayGeometryAsync(client, childDisplayId, matrixValues);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    @AnyThread
-    void reportPerceptibleAsync(@NonNull IBinder windowToken, boolean perceptible) {
-        try {
-            mTarget.reportPerceptibleAsync(windowToken, perceptible);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    @AnyThread
-    void removeImeSurfaceFromWindowAsync(@NonNull IBinder windowToken) {
-        try {
-            mTarget.removeImeSurfaceFromWindowAsync(windowToken);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    @AnyThread
-    void startStylusHandwriting(@NonNull IInputMethodClient client) {
-        try {
-            mTarget.startStylusHandwriting(client);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    @AnyThread
-    boolean isStylusHandwritingAvailableAsUser(@UserIdInt int userId) {
-        try {
-            return mTarget.isStylusHandwritingAvailableAsUser(userId);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    @AnyThread
-    void addVirtualStylusIdForTestSession(IInputMethodClient client) {
-        try {
-            mTarget.addVirtualStylusIdForTestSession(client);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    @AnyThread
-    void setStylusWindowIdleTimeoutForTest(
-            IInputMethodClient client, @DurationMillisLong long timeout) {
-        try {
-            mTarget.setStylusWindowIdleTimeoutForTest(client, timeout);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-}
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index 7d268a9..2099a31 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -16,11 +16,14 @@
 
 package android.view.inputmethod;
 
+import static android.view.inputmethod.TextBoundsInfoResult.CODE_UNSUPPORTED;
+
 import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.graphics.RectF;
 import android.inputmethodservice.InputMethodService;
 import android.os.Bundle;
 import android.os.Handler;
@@ -32,7 +35,9 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
 import java.util.concurrent.Executor;
+import java.util.function.Consumer;
 import java.util.function.IntConsumer;
 
 /**
@@ -1052,8 +1057,10 @@
      * used together with {@link #CURSOR_UPDATE_MONITOR}.
      * <p>
      * Note by default all of {@link #CURSOR_UPDATE_FILTER_EDITOR_BOUNDS},
-     * {@link #CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS} and
-     * {@link #CURSOR_UPDATE_FILTER_INSERTION_MARKER} are included but specifying them can
+     * {@link #CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS},
+     * {@link #CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS},
+     * {@link #CURSOR_UPDATE_FILTER_TEXT_APPEARANCE}, and
+     * {@link #CURSOR_UPDATE_FILTER_INSERTION_MARKER}, are included but specifying them can
      * filter-out others.
      * It can be CPU intensive to include all, filtering specific info is recommended.
      * </p>
@@ -1071,7 +1078,8 @@
      * <p>
      * Note by default all of {@link #CURSOR_UPDATE_FILTER_EDITOR_BOUNDS},
      * {@link #CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS},
-     * {@link #CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS} and
+     * {@link #CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS},
+     * {@link #CURSOR_UPDATE_FILTER_TEXT_APPEARANCE}, and
      * {@link #CURSOR_UPDATE_FILTER_INSERTION_MARKER}, are included but specifying them can
      * filter-out others.
      * It can be CPU intensive to include all, filtering specific info is recommended.
@@ -1087,6 +1095,7 @@
      * <p>
      * This flag can be used together with filters: {@link #CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS},
      * {@link #CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS},
+     * {@link #CURSOR_UPDATE_FILTER_TEXT_APPEARANCE},
      * {@link #CURSOR_UPDATE_FILTER_INSERTION_MARKER} and update flags
      * {@link #CURSOR_UPDATE_IMMEDIATE} and {@link #CURSOR_UPDATE_MONITOR}.
      * </p>
@@ -1102,8 +1111,8 @@
      * <p>
      * This flag can be combined with other filters: {@link #CURSOR_UPDATE_FILTER_EDITOR_BOUNDS},
      * {@link #CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS},
-     * {@link #CURSOR_UPDATE_FILTER_INSERTION_MARKER} and update flags
-     * {@link #CURSOR_UPDATE_IMMEDIATE} and {@link #CURSOR_UPDATE_MONITOR}.
+     * {@link #CURSOR_UPDATE_FILTER_TEXT_APPEARANCE}, {@link #CURSOR_UPDATE_FILTER_INSERTION_MARKER}
+     * and update flags {@link #CURSOR_UPDATE_IMMEDIATE} and {@link #CURSOR_UPDATE_MONITOR}.
      * </p>
      */
     int CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS = 1 << 3;
@@ -1118,8 +1127,8 @@
      * <p>
      * This flag can be combined with other filters: {@link #CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS},
      * {@link #CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS},
-     * {@link #CURSOR_UPDATE_FILTER_EDITOR_BOUNDS} and update flags {@link #CURSOR_UPDATE_IMMEDIATE}
-     * and {@link #CURSOR_UPDATE_MONITOR}.
+     * {@link #CURSOR_UPDATE_FILTER_TEXT_APPEARANCE}, {@link #CURSOR_UPDATE_FILTER_EDITOR_BOUNDS}
+     * and update flags {@link #CURSOR_UPDATE_IMMEDIATE} and {@link #CURSOR_UPDATE_MONITOR}.
      * </p>
      */
     int CURSOR_UPDATE_FILTER_INSERTION_MARKER = 1 << 4;
@@ -1133,14 +1142,29 @@
      * {@link InputConnection#requestCursorUpdates(int)} again with this flag off.
      * <p>
      * This flag can be combined with other filters: {@link #CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS},
-     * {@link #CURSOR_UPDATE_FILTER_EDITOR_BOUNDS}, {@link #CURSOR_UPDATE_FILTER_INSERTION_MARKER}
-     * and update flags {@link #CURSOR_UPDATE_IMMEDIATE}
-     * and {@link #CURSOR_UPDATE_MONITOR}.
+     * {@link #CURSOR_UPDATE_FILTER_EDITOR_BOUNDS}, {@link #CURSOR_UPDATE_FILTER_INSERTION_MARKER},
+     * {@link #CURSOR_UPDATE_FILTER_TEXT_APPEARANCE} and update flags
+     * {@link #CURSOR_UPDATE_IMMEDIATE} and {@link #CURSOR_UPDATE_MONITOR}.
      * </p>
      */
     int CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS = 1 << 5;
 
     /**
+     * The editor is requested to call
+     * {@link InputMethodManager#updateCursorAnchorInfo(android.view.View, CursorAnchorInfo)}
+     * with new text appearance info {@link CursorAnchorInfo#getTextAppearanceInfo()}}
+     * whenever cursor/anchor position is changed. To disable monitoring, call
+     * {@link InputConnection#requestCursorUpdates(int)} again with this flag off.
+     * <p>
+     * This flag can be combined with other filters: {@link #CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS},
+     * {@link #CURSOR_UPDATE_FILTER_EDITOR_BOUNDS}, {@link #CURSOR_UPDATE_FILTER_INSERTION_MARKER},
+     * {@link #CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS} and update flags
+     * {@link #CURSOR_UPDATE_IMMEDIATE} and {@link #CURSOR_UPDATE_MONITOR}.
+     * </p>
+     */
+    int CURSOR_UPDATE_FILTER_TEXT_APPEARANCE = 1 << 6;
+
+    /**
      * @hide
      */
     @Retention(RetentionPolicy.SOURCE)
@@ -1153,7 +1177,8 @@
      */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(value = {CURSOR_UPDATE_FILTER_EDITOR_BOUNDS, CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS,
-            CURSOR_UPDATE_FILTER_INSERTION_MARKER, CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS},
+            CURSOR_UPDATE_FILTER_INSERTION_MARKER, CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS,
+            CURSOR_UPDATE_FILTER_TEXT_APPEARANCE},
             flag = true, prefix = { "CURSOR_UPDATE_FILTER_" })
     @interface CursorUpdateFilter{}
 
@@ -1205,6 +1230,40 @@
         return false;
     }
 
+
+    /**
+     * Called by input method to request the {@link TextBoundsInfo} for a range of text which is
+     * covered by or in vicinity of the given {@code RectF}. It can be used as a supplementary
+     * method to implement the handwriting gesture API -
+     * {@link #performHandwritingGesture(HandwritingGesture, Executor, IntConsumer)}.
+     *
+     * <p><strong>Editor authors</strong>: It's preferred that the editor returns a
+     * {@link TextBoundsInfo} of all the text lines whose bounds intersect with the given
+     * {@code rectF}.
+     * </p>
+     *
+     * <p><strong>IME authors</strong>: This method is expensive when the text is long. Please
+     * consider that both the text bounds computation and IPC round-trip to send the data are time
+     * consuming. It's preferable to only request text bounds in smaller areas.
+     * </p>
+     *
+     * @param rectF the interested area where the text bounds are requested, in the screen
+     *              coordinates.
+     * @param executor the executor to run the callback.
+     * @param consumer the callback invoked by editor to return the result. It must return a
+     *                 non-null object.
+     *
+     * @see TextBoundsInfo
+     * @see android.view.inputmethod.TextBoundsInfoResult
+     */
+    default void requestTextBoundsInfo(
+            @NonNull RectF rectF, @NonNull @CallbackExecutor Executor executor,
+            @NonNull Consumer<TextBoundsInfoResult> consumer) {
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(consumer);
+        executor.execute(() -> consumer.accept(new TextBoundsInfoResult(CODE_UNSUPPORTED)));
+    }
+
     /**
      * Called by the system to enable application developers to specify a dedicated thread on which
      * {@link InputConnection} methods are called back.
diff --git a/core/java/android/view/inputmethod/InputConnectionWrapper.java b/core/java/android/view/inputmethod/InputConnectionWrapper.java
index 56beddf..7af96b6 100644
--- a/core/java/android/view/inputmethod/InputConnectionWrapper.java
+++ b/core/java/android/view/inputmethod/InputConnectionWrapper.java
@@ -20,6 +20,7 @@
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.graphics.RectF;
 import android.os.Bundle;
 import android.os.Handler;
 import android.view.KeyEvent;
@@ -27,6 +28,7 @@
 import com.android.internal.util.Preconditions;
 
 import java.util.concurrent.Executor;
+import java.util.function.Consumer;
 import java.util.function.IntConsumer;
 
 /**
@@ -347,6 +349,17 @@
      * @throws NullPointerException if the target is {@code null}.
      */
     @Override
+    public void requestTextBoundsInfo(
+            @NonNull RectF rectF, @NonNull @CallbackExecutor Executor executor,
+            @NonNull Consumer<TextBoundsInfoResult> consumer) {
+        mTarget.requestTextBoundsInfo(rectF, executor, consumer);
+    }
+
+    /**
+     * {@inheritDoc}
+     * @throws NullPointerException if the target is {@code null}.
+     */
+    @Override
     public Handler getHandler() {
         return mTarget.getHandler();
     }
diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java
index 69eed0a..d08816b 100644
--- a/core/java/android/view/inputmethod/InputMethodManager.java
+++ b/core/java/android/view/inputmethod/InputMethodManager.java
@@ -16,8 +16,6 @@
 
 package android.view.inputmethod;
 
-import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
-import static android.Manifest.permission.WRITE_SECURE_SETTINGS;
 import static android.view.inputmethod.InputConnection.CURSOR_UPDATE_IMMEDIATE;
 import static android.view.inputmethod.InputConnection.CURSOR_UPDATE_MONITOR;
 import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.DISPLAY_ID;
@@ -70,8 +68,6 @@
 import android.os.Message;
 import android.os.Process;
 import android.os.ResultReceiver;
-import android.os.ServiceManager;
-import android.os.ServiceManager.ServiceNotFoundException;
 import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.UserHandle;
@@ -130,6 +126,7 @@
 import java.util.Objects;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Consumer;
 
 /**
@@ -295,7 +292,7 @@
     private static final String SUBTYPE_MODE_VOICE = "voice";
 
     /**
-     * Provide this to {@link IInputMethodManagerInvoker#startInputOrWindowGainedFocus(int,
+     * Provide this to {@link IInputMethodManagerGlobalInvoker#startInputOrWindowGainedFocus(int,
      * IInputMethodClient, IBinder, int, int, int, EditorInfo,
      * com.android.internal.inputmethod.IRemoteInputConnection, IRemoteAccessibilityInputConnection,
      * int, int, ImeOnBackInvokedDispatcher)} to receive
@@ -422,16 +419,13 @@
             SystemProperties.getBoolean("debug.imm.optimize_noneditable_views", true);
 
     /**
-     * @deprecated Use {@link #mServiceInvoker} instead.
+     * @deprecated Use {@link IInputMethodManagerGlobalInvoker} instead.
      */
     @Deprecated
     @UnsupportedAppUsage
     final IInputMethodManager mService;
     private final Looper mMainLooper;
 
-    @NonNull
-    private final IInputMethodManagerInvoker mServiceInvoker;
-
     // For scheduling work on the main thread.  This also serves as our
     // global lock.
     // Remark on @UnsupportedAppUsage: there were context leaks on old versions
@@ -735,7 +729,7 @@
      * @hide
      */
     public void reportPerceptible(@NonNull IBinder windowToken, boolean perceptible) {
-        mServiceInvoker.reportPerceptibleAsync(windowToken, perceptible);
+        IInputMethodManagerGlobalInvoker.reportPerceptibleAsync(windowToken, perceptible);
     }
 
     private final class DelegateImpl implements
@@ -808,7 +802,7 @@
                 }
 
                 // ignore the result
-                mServiceInvoker.startInputOrWindowGainedFocus(
+                IInputMethodManagerGlobalInvoker.startInputOrWindowGainedFocus(
                         StartInputReason.WINDOW_FOCUS_GAIN_REPORT_ONLY, mClient,
                         viewForWindowFocus.getWindowToken(), startInputFlags, softInputMode,
                         windowFlags,
@@ -1353,6 +1347,10 @@
         return false;
     }
 
+    static boolean isInEditModeInternal() {
+        return isInEditMode();
+    }
+
     @NonNull
     private static InputMethodManager createInstance(int displayId, Looper looper) {
         return isInEditMode() ? createStubInstance(displayId, looper)
@@ -1361,12 +1359,9 @@
 
     @NonNull
     private static InputMethodManager createRealInstance(int displayId, Looper looper) {
-        final IInputMethodManager service;
-        try {
-            service = IInputMethodManager.Stub.asInterface(
-                    ServiceManager.getServiceOrThrow(Context.INPUT_METHOD_SERVICE));
-        } catch (ServiceNotFoundException e) {
-            throw new IllegalStateException(e);
+        final IInputMethodManager service = IInputMethodManagerGlobalInvoker.getService();
+        if (service == null) {
+            throw new IllegalStateException("IInputMethodManager is not available");
         }
         final InputMethodManager imm = new InputMethodManager(service, displayId, looper);
         // InputMethodManagerService#addClient() relies on Binder.getCalling{Pid, Uid}() to
@@ -1378,7 +1373,8 @@
         // 1) doing so has no effect for A and 2) doing so is sufficient for B.
         final long identity = Binder.clearCallingIdentity();
         try {
-            imm.mServiceInvoker.addClient(imm.mClient, imm.mFallbackInputConnection, displayId);
+            IInputMethodManagerGlobalInvoker.addClient(imm.mClient, imm.mFallbackInputConnection,
+                    displayId);
         } finally {
             Binder.restoreCallingIdentity(identity);
         }
@@ -1418,7 +1414,6 @@
 
     private InputMethodManager(@NonNull IInputMethodManager service, int displayId, Looper looper) {
         mService = service;  // For @UnsupportedAppUsage
-        mServiceInvoker = IInputMethodManagerInvoker.create(service);
         mMainLooper = looper;
         mH = new H(looper);
         mDisplayId = displayId;
@@ -1512,7 +1507,8 @@
         // We intentionally do not use UserHandle.getCallingUserId() here because for system
         // services InputMethodManagerInternal.getInputMethodListAsUser() should be used
         // instead.
-        return mServiceInvoker.getInputMethodList(UserHandle.myUserId(), DirectBootAwareness.AUTO);
+        return IInputMethodManagerGlobalInvoker.getInputMethodList(UserHandle.myUserId(),
+                DirectBootAwareness.AUTO);
     }
 
     /**
@@ -1528,12 +1524,18 @@
     /**
      * Returns {@code true} if currently selected IME supports Stylus handwriting & is enabled for
      * the given userId.
-     * If the method returns {@code false}, {@link #startStylusHandwriting(View)} shouldn't be
-     * called and Stylus touch should continue as normal touch input.
+     *
+     * <p>If the method returns {@code false}, {@link #startStylusHandwriting(View)} shouldn't be
+     * called and Stylus touch should continue as normal touch input.</p>
+     *
+     * <p>{@link Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required when and only when
+     * {@code userId} is different from the user id of the current process.</p>
+     *
      * @see #startStylusHandwriting(View)
      * @param userId user ID to query.
      * @hide
      */
+    @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
     public boolean isStylusHandwritingAvailableAsUser(@UserIdInt int userId) {
         final Context fallbackContext = ActivityThread.currentApplication();
         if (fallbackContext == null) {
@@ -1546,37 +1548,44 @@
             }
             return false;
         }
-        return mServiceInvoker.isStylusHandwritingAvailableAsUser(userId);
+        return IInputMethodManagerGlobalInvoker.isStylusHandwritingAvailableAsUser(userId);
     }
 
     /**
      * Returns the list of installed input methods for the specified user.
      *
+     * <p>{@link Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required when and only when
+     * {@code userId} is different from the user id of the current process.</p>
+     *
      * @param userId user ID to query
      * @return {@link List} of {@link InputMethodInfo}.
      * @hide
      */
     @TestApi
-    @RequiresPermission(INTERACT_ACROSS_USERS_FULL)
     @NonNull
+    @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
     public List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId) {
-        return mServiceInvoker.getInputMethodList(userId, DirectBootAwareness.AUTO);
+        return IInputMethodManagerGlobalInvoker.getInputMethodList(userId,
+                DirectBootAwareness.AUTO);
     }
 
     /**
      * Returns the list of installed input methods for the specified user.
      *
+     * <p>{@link Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required when and only when
+     * {@code userId} is different from the user id of the current process.</p>
+     *
      * @param userId user ID to query
      * @param directBootAwareness {@code true} if caller want to query installed input methods list
      * on user locked state.
      * @return {@link List} of {@link InputMethodInfo}.
      * @hide
      */
-    @RequiresPermission(INTERACT_ACROSS_USERS_FULL)
     @NonNull
+    @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
     public List<InputMethodInfo> getInputMethodListAsUser(@UserIdInt int userId,
             @DirectBootAwareness int directBootAwareness) {
-        return mServiceInvoker.getInputMethodList(userId, directBootAwareness);
+        return IInputMethodManagerGlobalInvoker.getInputMethodList(userId, directBootAwareness);
     }
 
     /**
@@ -1591,19 +1600,22 @@
         // We intentionally do not use UserHandle.getCallingUserId() here because for system
         // services InputMethodManagerInternal.getEnabledInputMethodListAsUser() should be used
         // instead.
-        return mServiceInvoker.getEnabledInputMethodList(UserHandle.myUserId());
+        return IInputMethodManagerGlobalInvoker.getEnabledInputMethodList(UserHandle.myUserId());
     }
 
     /**
      * Returns the list of enabled input methods for the specified user.
      *
+     * <p>{@link Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required when and only when
+     * {@code userId} is different from the user id of the current process.</p>
+     *
      * @param userId user ID to query
      * @return {@link List} of {@link InputMethodInfo}.
      * @hide
      */
-    @RequiresPermission(INTERACT_ACROSS_USERS_FULL)
+    @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
     public List<InputMethodInfo> getEnabledInputMethodListAsUser(@UserIdInt int userId) {
-        return mServiceInvoker.getEnabledInputMethodList(userId);
+        return IInputMethodManagerGlobalInvoker.getEnabledInputMethodList(userId);
     }
 
     /**
@@ -1620,7 +1632,7 @@
     @NonNull
     public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(@Nullable InputMethodInfo imi,
             boolean allowsImplicitlyEnabledSubtypes) {
-        return mServiceInvoker.getEnabledInputMethodSubtypeList(
+        return IInputMethodManagerGlobalInvoker.getEnabledInputMethodSubtypeList(
                 imi == null ? null : imi.getId(),
                 allowsImplicitlyEnabledSubtypes,
                 UserHandle.myUserId());
@@ -1904,8 +1916,11 @@
      * a result receiver: explicitly request that the current input method's
      * soft input area be shown to the user, if needed.
      *
-     * @param view The currently focused view, which would like to receive
-     * soft keyboard input.
+     * @param view The currently focused view, which would like to receive soft keyboard input.
+     *             Note that this view is only considered focused here if both it itself has
+     *             {@link View#isFocused view focus}, and its containing window has
+     *             {@link View#hasWindowFocus window focus}. Otherwise the call fails and
+     *             returns {@code false}.
      * @param flags Provides additional operating flags.  Currently may be
      * 0 or have the {@link #SHOW_IMPLICIT} bit set.
      */
@@ -1967,8 +1982,11 @@
      * can be garbage collected regardless of the lifetime of
      * {@link ResultReceiver}.
      *
-     * @param view The currently focused view, which would like to receive
-     * soft keyboard input.
+     * @param view The currently focused view, which would like to receive soft keyboard input.
+     *             Note that this view is only considered focused here if both it itself has
+     *             {@link View#isFocused view focus}, and its containing window has
+     *             {@link View#hasWindowFocus window focus}. Otherwise the call fails and
+     *             returns {@code false}.
      * @param flags Provides additional operating flags.  Currently may be
      * 0 or have the {@link #SHOW_IMPLICIT} bit set.
      * @param resultReceiver If non-null, this will be called by the IME when
@@ -2003,7 +2021,7 @@
             mH.executeOrSendMessage(Message.obtain(mH, MSG_ON_SHOW_REQUESTED));
             Log.d(TAG, "showSoftInput() view=" + view + " flags=" + flags + " reason="
                     + InputMethodDebug.softInputDisplayReasonToString(reason));
-            return mServiceInvoker.showSoftInput(
+            return IInputMethodManagerGlobalInvoker.showSoftInput(
                     mClient,
                     view.getWindowToken(),
                     flags,
@@ -2035,7 +2053,7 @@
             // Makes sure to call ImeInsetsSourceConsumer#onShowRequested on the UI thread.
             // TODO(b/229426865): call WindowInsetsController#show instead.
             mH.executeOrSendMessage(Message.obtain(mH, MSG_ON_SHOW_REQUESTED));
-            mServiceInvoker.showSoftInput(
+            IInputMethodManagerGlobalInvoker.showSoftInput(
                     mClient,
                     mCurRootView.getView().getWindowToken(),
                     flags,
@@ -2116,8 +2134,8 @@
                 return false;
             }
 
-            return mServiceInvoker.hideSoftInput(mClient, windowToken, flags, resultReceiver,
-                    reason);
+            return IInputMethodManagerGlobalInvoker.hideSoftInput(mClient, windowToken, flags,
+                    resultReceiver, reason);
         }
     }
 
@@ -2165,7 +2183,7 @@
                 return;
             }
 
-            mServiceInvoker.startStylusHandwriting(mClient);
+            IInputMethodManagerGlobalInvoker.startStylusHandwriting(mClient);
             // TODO(b/210039666): do we need any extra work for supporting non-native
             //   UI toolkits?
         }
@@ -2348,7 +2366,11 @@
      * Starts an input connection from the served view that gains the window focus.
      * Note that this method should *NOT* be called inside of {@code mH} lock to prevent start input
      * background thread may blocked by other methods which already inside {@code mH} lock.
+     *
+     * <p>{@link Manifest.permission#INTERACT_ACROSS_USERS_FULL} is required when and only when
+     * {@code userId} is different from the user id of the current process.</p>
      */
+    @RequiresPermission(value = Manifest.permission.INTERACT_ACROSS_USERS_FULL, conditional = true)
     private boolean startInputInner(@StartInputReason int startInputReason,
             @Nullable IBinder windowGainingFocus, @StartInputFlags int startInputFlags,
             @SoftInputModeFlags int softInputMode, int windowFlags) {
@@ -2498,7 +2520,7 @@
             }
             final int targetUserId = editorInfo.targetInputMethodUser != null
                     ? editorInfo.targetInputMethodUser.getIdentifier() : UserHandle.myUserId();
-            res = mServiceInvoker.startInputOrWindowGainedFocus(
+            res = IInputMethodManagerGlobalInvoker.startInputOrWindowGainedFocus(
                     startInputReason, mClient, windowGainingFocus, startInputFlags,
                     softInputMode, windowFlags, editorInfo, servedInputConnection,
                     servedInputConnection == null ? null
@@ -2592,9 +2614,10 @@
      * @hide
      */
     @TestApi
+    @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD)
     public void addVirtualStylusIdForTestSession() {
         synchronized (mH) {
-            mServiceInvoker.addVirtualStylusIdForTestSession(mClient);
+            IInputMethodManagerGlobalInvoker.addVirtualStylusIdForTestSession(mClient);
         }
     }
 
@@ -2604,11 +2627,11 @@
      * @param timeout to set in milliseconds. To reset to default, use a value <= zero.
      * @hide
      */
-    @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD)
     @TestApi
+    @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD)
     public void setStylusWindowIdleTimeoutForTest(@DurationMillisLong long timeout) {
         synchronized (mH) {
-            mServiceInvoker.setStylusWindowIdleTimeoutForTest(mClient, timeout);
+            IInputMethodManagerGlobalInvoker.setStylusWindowIdleTimeoutForTest(mClient, timeout);
         }
     }
 
@@ -2745,7 +2768,7 @@
                 Log.w(TAG, "No current root view, ignoring closeCurrentInput()");
                 return;
             }
-            mServiceInvoker.hideSoftInput(
+            IInputMethodManagerGlobalInvoker.hideSoftInput(
                     mClient,
                     mCurRootView.getView().getWindowToken(),
                     HIDE_NOT_ALWAYS,
@@ -2821,7 +2844,7 @@
         synchronized (mH) {
             if (isImeSessionAvailableLocked() && mCurRootView != null
                     && mCurRootView.getWindowToken() == windowToken) {
-                mServiceInvoker.hideSoftInput(mClient, windowToken, 0 /* flags */,
+                IInputMethodManagerGlobalInvoker.hideSoftInput(mClient, windowToken, 0 /* flags */,
                         null /* resultReceiver */,
                         SoftInputShowHideReason.HIDE_SOFT_INPUT_BY_INSETS_API);
             }
@@ -2835,7 +2858,7 @@
      */
     public void removeImeSurface(@NonNull IBinder windowToken) {
         synchronized (mH) {
-            mServiceInvoker.removeImeSurfaceFromWindowAsync(windowToken);
+            IInputMethodManagerGlobalInvoker.removeImeSurfaceFromWindowAsync(windowToken);
         }
     }
 
@@ -3084,7 +3107,7 @@
      *
      * <p>On Android {@link Build.VERSION_CODES#Q} and later devices, the undocumented behavior that
      * token can be {@code null} when the caller has
-     * {@link android.Manifest.permission#WRITE_SECURE_SETTINGS} is deprecated. Instead, update
+     * {@link Manifest.permission#WRITE_SECURE_SETTINGS} is deprecated. Instead, update
      * {@link android.provider.Settings.Secure#DEFAULT_INPUT_METHOD} and
      * {@link android.provider.Settings.Secure#SELECTED_INPUT_METHOD_SUBTYPE} directly.</p>
      *
@@ -3116,7 +3139,7 @@
             if (fallbackContext == null) {
                 return;
             }
-            if (fallbackContext.checkSelfPermission(WRITE_SECURE_SETTINGS)
+            if (fallbackContext.checkSelfPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
                     != PackageManager.PERMISSION_GRANTED) {
                 return;
             }
@@ -3153,7 +3176,7 @@
      * from an application or a service which has a token of the currently active input method.
      *
      * <p>On Android {@link Build.VERSION_CODES#Q} and later devices, {@code token} cannot be
-     * {@code null} even with {@link android.Manifest.permission#WRITE_SECURE_SETTINGS}. Instead,
+     * {@code null} even with {@link Manifest.permission#WRITE_SECURE_SETTINGS}. Instead,
      * update {@link android.provider.Settings.Secure#DEFAULT_INPUT_METHOD} and
      * {@link android.provider.Settings.Secure#SELECTED_INPUT_METHOD_SUBTYPE} directly.</p>
      *
@@ -3435,17 +3458,18 @@
      * @param displayId The ID of the display where the chooser dialog should be shown.
      * @hide
      */
-    @RequiresPermission(WRITE_SECURE_SETTINGS)
+    @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
     public void showInputMethodPickerFromSystem(boolean showAuxiliarySubtypes, int displayId) {
         final int mode = showAuxiliarySubtypes
                 ? SHOW_IM_PICKER_MODE_INCLUDE_AUXILIARY_SUBTYPES
                 : SHOW_IM_PICKER_MODE_EXCLUDE_AUXILIARY_SUBTYPES;
-        mServiceInvoker.showInputMethodPickerFromSystem(mClient, mode, displayId);
+        IInputMethodManagerGlobalInvoker.showInputMethodPickerFromSystem(mode, displayId);
     }
 
     @GuardedBy("mH")
     private void showInputMethodPickerLocked() {
-        mServiceInvoker.showInputMethodPickerFromClient(mClient, SHOW_IM_PICKER_MODE_AUTO);
+        IInputMethodManagerGlobalInvoker.showInputMethodPickerFromClient(mClient,
+                SHOW_IM_PICKER_MODE_AUTO);
     }
 
     /**
@@ -3460,8 +3484,9 @@
      * @hide
      */
     @TestApi
+    @RequiresPermission(Manifest.permission.TEST_INPUT_METHOD)
     public boolean isInputMethodPickerShown() {
-        return mServiceInvoker.isInputMethodPickerShownForTest();
+        return IInputMethodManagerGlobalInvoker.isInputMethodPickerShownForTest();
     }
 
     /**
@@ -3500,7 +3525,7 @@
      */
     @Nullable
     public InputMethodSubtype getCurrentInputMethodSubtype() {
-        return mServiceInvoker.getCurrentInputMethodSubtype(UserHandle.myUserId());
+        return IInputMethodManagerGlobalInvoker.getCurrentInputMethodSubtype(UserHandle.myUserId());
     }
 
     /**
@@ -3512,11 +3537,11 @@
      *             {@link InputMethodService#switchInputMethod(String, InputMethodSubtype)}, which
      *             does not require any permission as long as the caller is the current IME.
      *             If the calling process is some privileged app that already has
-     *             {@link android.Manifest.permission#WRITE_SECURE_SETTINGS} permission, just
+     *             {@link Manifest.permission#WRITE_SECURE_SETTINGS} permission, just
      *             directly update {@link Settings.Secure#SELECTED_INPUT_METHOD_SUBTYPE}.
      */
     @Deprecated
-    @RequiresPermission(WRITE_SECURE_SETTINGS)
+    @RequiresPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
     public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) {
         if (Process.myUid() == Process.SYSTEM_UID) {
             Log.w(TAG, "System process should not call setCurrentInputMethodSubtype() because "
@@ -3533,7 +3558,7 @@
         if (fallbackContext == null) {
             return false;
         }
-        if (fallbackContext.checkSelfPermission(WRITE_SECURE_SETTINGS)
+        if (fallbackContext.checkSelfPermission(Manifest.permission.WRITE_SECURE_SETTINGS)
                 != PackageManager.PERMISSION_GRANTED) {
             return false;
         }
@@ -3545,7 +3570,7 @@
             return false;
         }
         final List<InputMethodSubtype> enabledSubtypes =
-                mServiceInvoker.getEnabledInputMethodSubtypeList(imeId, true,
+                IInputMethodManagerGlobalInvoker.getEnabledInputMethodSubtypeList(imeId, true,
                         UserHandle.myUserId());
         final int numSubtypes = enabledSubtypes.size();
         for (int i = 0; i < numSubtypes; ++i) {
@@ -3611,7 +3636,33 @@
     @UnsupportedAppUsage(trackingBug = 204906124, maxTargetSdk = Build.VERSION_CODES.TIRAMISU,
             publicAlternatives = "Use {@link android.view.WindowInsets} instead")
     public int getInputMethodWindowVisibleHeight() {
-        return mServiceInvoker.getInputMethodWindowVisibleHeight(mClient);
+        return IInputMethodManagerGlobalInvoker.getInputMethodWindowVisibleHeight(mClient);
+    }
+
+    /**
+     * {@code true} means that
+     * {@link RemoteInputConnectionImpl#requestCursorUpdatesInternal(int, int, int)} returns
+     * {@code false} when the IME client and the IME run in different displays.
+     */
+    final AtomicBoolean mRequestCursorUpdateDisplayIdCheck = new AtomicBoolean(true);
+
+    /**
+     * Controls the display ID mismatch validation in
+     * {@link RemoteInputConnectionImpl#requestCursorUpdatesInternal(int, int, int)}.
+     *
+     * <p>{@link #updateCursorAnchorInfo(View, CursorAnchorInfo)} is not guaranteed to work
+     * correctly when the IME client and the IME run in different displays.  This is why
+     * {@link RemoteInputConnectionImpl#requestCursorUpdatesInternal(int, int, int)} returns
+     * {@code false} by default when the display ID does not match. This method allows special apps
+     * to override this behavior when they are sure that it should work.</p>
+     *
+     * <p>By default the validation is enabled.</p>
+     *
+     * @param enabled {@code false} to disable the display ID validation.
+     * @hide
+     */
+    public void setRequestCursorUpdateDisplayIdCheck(boolean enabled) {
+        mRequestCursorUpdateDisplayIdCheck.set(enabled);
     }
 
     /**
@@ -3631,7 +3682,8 @@
             matrixValues = new float[9];
             matrix.getValues(matrixValues);
         }
-        mServiceInvoker.reportVirtualDisplayGeometryAsync(mClient, childDisplayId, matrixValues);
+        IInputMethodManagerGlobalInvoker.reportVirtualDisplayGeometryAsync(mClient, childDisplayId,
+                matrixValues);
     }
 
     /**
@@ -3738,7 +3790,8 @@
     @Deprecated
     public void setAdditionalInputMethodSubtypes(@NonNull String imiId,
             @NonNull InputMethodSubtype[] subtypes) {
-        mServiceInvoker.setAdditionalInputMethodSubtypes(imiId, subtypes, UserHandle.myUserId());
+        IInputMethodManagerGlobalInvoker.setAdditionalInputMethodSubtypes(imiId, subtypes,
+                UserHandle.myUserId());
     }
 
     /**
@@ -3787,8 +3840,8 @@
      */
     public void setExplicitlyEnabledInputMethodSubtypes(@NonNull String imiId,
             @NonNull int[] subtypeHashCodes) {
-        mServiceInvoker.setExplicitlyEnabledInputMethodSubtypes(imiId, subtypeHashCodes,
-                UserHandle.myUserId());
+        IInputMethodManagerGlobalInvoker.setExplicitlyEnabledInputMethodSubtypes(imiId,
+                subtypeHashCodes, UserHandle.myUserId());
     }
 
     /**
@@ -3798,7 +3851,7 @@
      */
     @Nullable
     public InputMethodSubtype getLastInputMethodSubtype() {
-        return mServiceInvoker.getLastInputMethodSubtype(UserHandle.myUserId());
+        return IInputMethodManagerGlobalInvoker.getLastInputMethodSubtype(UserHandle.myUserId());
     }
 
     /**
diff --git a/core/java/android/view/inputmethod/InputMethodManagerGlobal.java b/core/java/android/view/inputmethod/InputMethodManagerGlobal.java
new file mode 100644
index 0000000..5df9fd1
--- /dev/null
+++ b/core/java/android/view/inputmethod/InputMethodManagerGlobal.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod;
+
+import android.Manifest;
+import android.annotation.AnyThread;
+import android.annotation.Nullable;
+import android.annotation.RequiresNoPermission;
+import android.annotation.RequiresPermission;
+import android.os.RemoteException;
+
+import com.android.internal.inputmethod.ImeTracing;
+import com.android.internal.view.IInputMethodManager;
+
+import java.util.function.Consumer;
+
+/**
+ * Defines a set of static methods that can be used globally by framework classes.
+ *
+ * @hide
+ */
+public class InputMethodManagerGlobal {
+    /**
+     * @return {@code true} if IME tracing is currently is available.
+     */
+    @AnyThread
+    public static boolean isImeTraceAvailable() {
+        return IInputMethodManagerGlobalInvoker.isAvailable();
+    }
+
+    /**
+     * Invokes {@link IInputMethodManager#startProtoDump(byte[], int, String)}.
+     *
+     * @param protoDump client or service side information to be stored by the server
+     * @param source where the information is coming from, refer to
+     *               {@link ImeTracing#IME_TRACING_FROM_CLIENT} and
+     *               {@link ImeTracing#IME_TRACING_FROM_IMS}
+     * @param where where the information is coming from.
+     * @param exceptionHandler an optional {@link RemoteException} handler.
+     */
+    @AnyThread
+    @RequiresNoPermission
+    public static void startProtoDump(byte[] protoDump, int source, String where,
+            @Nullable Consumer<RemoteException> exceptionHandler) {
+        IInputMethodManagerGlobalInvoker.startProtoDump(protoDump, source, where, exceptionHandler);
+    }
+
+    /**
+     * Invokes {@link IInputMethodManager#startImeTrace()}.
+     *
+     * @param exceptionHandler an optional {@link RemoteException} handler.
+     */
+    @AnyThread
+    @RequiresPermission(Manifest.permission.CONTROL_UI_TRACING)
+    public static void startImeTrace(@Nullable Consumer<RemoteException> exceptionHandler) {
+        IInputMethodManagerGlobalInvoker.startImeTrace(exceptionHandler);
+    }
+
+    /**
+     * Invokes {@link IInputMethodManager#stopImeTrace()}.
+     *
+     * @param exceptionHandler an optional {@link RemoteException} handler.
+     */
+    @AnyThread
+    @RequiresPermission(Manifest.permission.CONTROL_UI_TRACING)
+    public static void stopImeTrace(@Nullable Consumer<RemoteException> exceptionHandler) {
+        IInputMethodManagerGlobalInvoker.stopImeTrace(exceptionHandler);
+    }
+
+    /**
+     * Invokes {@link IInputMethodManager#isImeTraceEnabled()}.
+     *
+     * @return The return value of {@link IInputMethodManager#isImeTraceEnabled()}.
+     */
+    @AnyThread
+    @RequiresNoPermission
+    public static boolean isImeTraceEnabled() {
+        return IInputMethodManagerGlobalInvoker.isImeTraceEnabled();
+    }
+
+    /**
+     * Invokes {@link IInputMethodManager#removeImeSurface()}
+     *
+     * @param exceptionHandler an optional {@link RemoteException} handler.
+     */
+    @AnyThread
+    @RequiresPermission(Manifest.permission.INTERNAL_SYSTEM_WINDOW)
+    public static void removeImeSurface(@Nullable Consumer<RemoteException> exceptionHandler) {
+        IInputMethodManagerGlobalInvoker.removeImeSurface(exceptionHandler);
+    }
+}
diff --git a/core/java/android/view/inputmethod/InsertGesture.java b/core/java/android/view/inputmethod/InsertGesture.java
index 9f03289..0449a16 100644
--- a/core/java/android/view/inputmethod/InsertGesture.java
+++ b/core/java/android/view/inputmethod/InsertGesture.java
@@ -21,7 +21,6 @@
 import android.graphics.PointF;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.text.TextUtils;
 import android.widget.TextView;
 
 import androidx.annotation.Nullable;
@@ -52,7 +51,8 @@
         mPoint = source.readTypedObject(PointF.CREATOR);
     }
 
-    /** Returns the text that will be inserted at {@link #getInsertionPoint()} **/
+    /** Returns the text that will be inserted at {@link #getInsertionPoint()}. When text is
+     * empty, cursor should be moved the insertion point. **/
     @NonNull
     public String getTextToInsert() {
         return mTextToInsert;
@@ -75,7 +75,11 @@
         private PointF mPoint;
         private String mFallbackText;
 
-        /** set the text that will be inserted at {@link #setInsertionPoint(PointF)} **/
+        /**
+         * Set the text that will be inserted at {@link #setInsertionPoint(PointF)}. When set with
+         * an empty string, cursor will be moved to {@link #getInsertionPoint()} and no text
+         * would be inserted.
+         */
         @NonNull
         @SuppressLint("MissingGetterMatchingBuilder")
         public Builder setTextToInsert(@NonNull String text) {
@@ -114,8 +118,8 @@
             if (mPoint == null) {
                 throw new IllegalArgumentException("Insertion point must be set.");
             }
-            if (TextUtils.isEmpty(mText)) {
-                throw new IllegalArgumentException("Text to insert must be non-empty.");
+            if (mText == null) {
+                throw new IllegalArgumentException("Text to insert must be set.");
             }
             return new InsertGesture(mText, mPoint, mFallbackText);
         }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl b/core/java/android/view/inputmethod/ParcelableHandwritingGesture.aidl
similarity index 80%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
copy to core/java/android/view/inputmethod/ParcelableHandwritingGesture.aidl
index 1550ab3..ffadf82 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
+++ b/core/java/android/view/inputmethod/ParcelableHandwritingGesture.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.systemui.shared.system;
+package android.view.inputmethod;
 
-parcelable RemoteTransitionCompat;
+parcelable ParcelableHandwritingGesture;
diff --git a/core/java/android/view/inputmethod/ParcelableHandwritingGesture.java b/core/java/android/view/inputmethod/ParcelableHandwritingGesture.java
new file mode 100644
index 0000000..e4066fc
--- /dev/null
+++ b/core/java/android/view/inputmethod/ParcelableHandwritingGesture.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * A generic container of parcelable {@link HandwritingGesture}.
+ *
+ * @hide
+ */
+public final class ParcelableHandwritingGesture implements Parcelable {
+    @NonNull
+    private final HandwritingGesture mGesture;
+    @NonNull
+    private final Parcelable mGestureAsParcelable;
+
+    private ParcelableHandwritingGesture(@NonNull HandwritingGesture gesture) {
+        mGesture = gesture;
+        // For fail-fast.
+        mGestureAsParcelable = (Parcelable) gesture;
+    }
+
+    /**
+     * Creates {@link ParcelableHandwritingGesture} from {@link HandwritingGesture}, which also
+     * implements {@link Parcelable}.
+     *
+     * @param gesture {@link HandwritingGesture} object to be stored.
+     * @return {@link ParcelableHandwritingGesture} to be stored in {@link Parcel}.
+     */
+    @NonNull
+    public static ParcelableHandwritingGesture of(@NonNull HandwritingGesture gesture) {
+        return new ParcelableHandwritingGesture(Objects.requireNonNull(gesture));
+    }
+
+    /**
+     * @return {@link HandwritingGesture} object stored in this container.
+     */
+    @NonNull
+    public HandwritingGesture get() {
+        return mGesture;
+    }
+
+    private static HandwritingGesture createFromParcelInternal(
+            @HandwritingGesture.GestureType int gestureType, @NonNull Parcel parcel) {
+        switch (gestureType) {
+            case HandwritingGesture.GESTURE_TYPE_NONE:
+                throw new UnsupportedOperationException("GESTURE_TYPE_NONE is not supported");
+            case HandwritingGesture.GESTURE_TYPE_SELECT:
+                return SelectGesture.CREATOR.createFromParcel(parcel);
+            case HandwritingGesture.GESTURE_TYPE_SELECT_RANGE:
+                return SelectRangeGesture.CREATOR.createFromParcel(parcel);
+            case HandwritingGesture.GESTURE_TYPE_INSERT:
+                return InsertGesture.CREATOR.createFromParcel(parcel);
+            case HandwritingGesture.GESTURE_TYPE_DELETE:
+                return DeleteGesture.CREATOR.createFromParcel(parcel);
+            case HandwritingGesture.GESTURE_TYPE_DELETE_RANGE:
+                return DeleteRangeGesture.CREATOR.createFromParcel(parcel);
+            case HandwritingGesture.GESTURE_TYPE_JOIN_OR_SPLIT:
+                return JoinOrSplitGesture.CREATOR.createFromParcel(parcel);
+            case HandwritingGesture.GESTURE_TYPE_REMOVE_SPACE:
+                return RemoveSpaceGesture.CREATOR.createFromParcel(parcel);
+            default:
+                throw new UnsupportedOperationException("Unknown type=" + gestureType);
+        }
+    }
+
+    public static final Creator<ParcelableHandwritingGesture> CREATOR = new Parcelable.Creator<>() {
+        @Override
+        public ParcelableHandwritingGesture createFromParcel(Parcel in) {
+            final int gestureType = in.readInt();
+            return new ParcelableHandwritingGesture(createFromParcelInternal(gestureType, in));
+        }
+
+        @Override
+        public ParcelableHandwritingGesture[] newArray(int size) {
+            return new ParcelableHandwritingGesture[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return mGestureAsParcelable.describeContents();
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mGesture.getGestureType());
+        mGestureAsParcelable.writeToParcel(dest, flags);
+    }
+}
diff --git a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
index f2b7099..c01c310 100644
--- a/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
+++ b/core/java/android/view/inputmethod/RemoteInputConnectionImpl.java
@@ -28,6 +28,7 @@
 import android.annotation.AnyThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.graphics.RectF;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
@@ -982,62 +983,9 @@
 
     @Dispatching(cancellable = true)
     @Override
-    public void performHandwritingSelectGesture(
-            InputConnectionCommandHeader header, SelectGesture gesture,
+    public void performHandwritingGesture(
+            InputConnectionCommandHeader header, ParcelableHandwritingGesture gestureContainer,
             ResultReceiver resultReceiver) {
-        performHandwritingGestureInternal(header, gesture, resultReceiver);
-    }
-
-    @Dispatching(cancellable = true)
-    @Override
-    public void performHandwritingSelectRangeGesture(
-            InputConnectionCommandHeader header, SelectRangeGesture gesture,
-            ResultReceiver resultReceiver) {
-        performHandwritingGestureInternal(header, gesture, resultReceiver);
-    }
-
-    @Dispatching(cancellable = true)
-    @Override
-    public void performHandwritingInsertGesture(
-            InputConnectionCommandHeader header, InsertGesture gesture,
-            ResultReceiver resultReceiver) {
-        performHandwritingGestureInternal(header, gesture, resultReceiver);
-    }
-
-    @Dispatching(cancellable = true)
-    @Override
-    public void performHandwritingDeleteGesture(
-            InputConnectionCommandHeader header, DeleteGesture gesture,
-            ResultReceiver resultReceiver) {
-        performHandwritingGestureInternal(header, gesture, resultReceiver);
-    }
-
-    @Dispatching(cancellable = true)
-    @Override
-    public void performHandwritingDeleteRangeGesture(
-            InputConnectionCommandHeader header, DeleteRangeGesture gesture,
-            ResultReceiver resultReceiver) {
-        performHandwritingGestureInternal(header, gesture, resultReceiver);
-    }
-
-    @Dispatching(cancellable = true)
-    @Override
-    public void performHandwritingRemoveSpaceGesture(
-            InputConnectionCommandHeader header, RemoveSpaceGesture gesture,
-            ResultReceiver resultReceiver) {
-        performHandwritingGestureInternal(header, gesture, resultReceiver);
-    }
-
-    @Dispatching(cancellable = true)
-    @Override
-    public void performHandwritingJoinOrSplitGesture(
-            InputConnectionCommandHeader header, JoinOrSplitGesture gesture,
-            ResultReceiver resultReceiver) {
-        performHandwritingGestureInternal(header, gesture, resultReceiver);
-    }
-
-    private <T extends HandwritingGesture> void performHandwritingGestureInternal(
-            InputConnectionCommandHeader header,  T gesture, ResultReceiver resultReceiver) {
         dispatchWithTracing("performHandwritingGesture", () -> {
             if (header.mSessionId != mCurrentSessionId.get()) {
                 if (resultReceiver != null) {
@@ -1059,7 +1007,7 @@
             // TODO(210039666): implement Cleaner to return HANDWRITING_GESTURE_RESULT_UNKNOWN if
             //  editor doesn't return any type.
             ic.performHandwritingGesture(
-                    gesture,
+                    gestureContainer.get(),
                     resultReceiver != null ? Runnable::run : null,
                     resultReceiver != null
                             ? (resultCode) -> resultReceiver.send(resultCode, null /* resultData */)
@@ -1067,27 +1015,6 @@
         });
     }
 
-    /**
-     * Dispatches {@link InputConnection#requestCursorUpdates(int)}.
-     *
-     * <p>This method is intended to be called only from {@link InputMethodManager}.</p>
-     * @param cursorUpdateMode the mode for {@link InputConnection#requestCursorUpdates(int, int)}
-     * @param cursorUpdateFilter the filter for
-     *      {@link InputConnection#requestCursorUpdates(int, int)}
-     * @param imeDisplayId displayId on which IME is displayed.
-     */
-    @Dispatching(cancellable = true)
-    public void requestCursorUpdatesFromImm(int cursorUpdateMode, int cursorUpdateFilter,
-            int imeDisplayId) {
-        final int currentSessionId = mCurrentSessionId.get();
-        dispatchWithTracing("requestCursorUpdatesFromImm", () -> {
-            if (currentSessionId != mCurrentSessionId.get()) {
-                return;  // cancelled
-            }
-            requestCursorUpdatesInternal(cursorUpdateMode, cursorUpdateFilter, imeDisplayId);
-        });
-    }
-
     @Dispatching(cancellable = true)
     @Override
     public void requestCursorUpdates(InputConnectionCommandHeader header, int cursorUpdateMode,
@@ -1123,7 +1050,8 @@
             Log.w(TAG, "requestCursorAnchorInfo on inactive InputConnection");
             return false;
         }
-        if (mParentInputMethodManager.getDisplayId() != imeDisplayId
+        if (mParentInputMethodManager.mRequestCursorUpdateDisplayIdCheck.get()
+                && mParentInputMethodManager.getDisplayId() != imeDisplayId
                 && !mParentInputMethodManager.hasVirtualDisplayToScreenMatrix()) {
             // requestCursorUpdates() is not currently supported across displays.
             return false;
@@ -1147,6 +1075,36 @@
 
     @Dispatching(cancellable = true)
     @Override
+    public void requestTextBoundsInfo(
+            InputConnectionCommandHeader header, RectF rectF,
+            @NonNull ResultReceiver resultReceiver) {
+        dispatchWithTracing("requestTextBoundsInfo", () -> {
+            if (header.mSessionId != mCurrentSessionId.get()) {
+                resultReceiver.send(TextBoundsInfoResult.CODE_CANCELLED, null);
+                return;  // cancelled
+            }
+            InputConnection ic = getInputConnection();
+            if (ic == null || !isActive()) {
+                Log.w(TAG, "requestTextBoundsInfo on inactive InputConnection");
+                resultReceiver.send(TextBoundsInfoResult.CODE_CANCELLED, null);
+                return;
+            }
+
+            ic.requestTextBoundsInfo(
+                    rectF,
+                    Runnable::run,
+                    (textBoundsInfoResult) -> {
+                        final int resultCode = textBoundsInfoResult.getResultCode();
+                        final TextBoundsInfo textBoundsInfo =
+                                textBoundsInfoResult.getTextBoundsInfo();
+                        resultReceiver.send(resultCode,
+                                textBoundsInfo == null ? null : textBoundsInfo.toBundle());
+                    });
+        });
+    }
+
+    @Dispatching(cancellable = true)
+    @Override
     public void commitContent(InputConnectionCommandHeader header,
             InputContentInfo inputContentInfo, int flags, Bundle opts,
             AndroidFuture future /* T=Boolean */) {
diff --git a/core/java/android/view/inputmethod/TextAppearanceInfo.java b/core/java/android/view/inputmethod/TextAppearanceInfo.java
new file mode 100644
index 0000000..1df4fc5
--- /dev/null
+++ b/core/java/android/view/inputmethod/TextAppearanceInfo.java
@@ -0,0 +1,509 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod;
+
+import android.annotation.ColorInt;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.Px;
+import android.content.res.ColorStateList;
+import android.graphics.Paint;
+import android.graphics.Typeface;
+import android.graphics.fonts.FontStyle;
+import android.graphics.text.LineBreakConfig;
+import android.inputmethodservice.InputMethodService;
+import android.os.LocaleList;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.InputFilter;
+import android.widget.TextView;
+
+import java.util.Objects;
+
+/**
+ * Information about text appearance in an editor, passed through
+ * {@link CursorAnchorInfo} for use by {@link InputMethodService}.
+ *
+ * @see TextView
+ * @see Paint
+ * @see CursorAnchorInfo.Builder#setTextAppearanceInfo(TextAppearanceInfo)
+ * @see CursorAnchorInfo#getTextAppearanceInfo()
+ */
+public final class TextAppearanceInfo implements Parcelable {
+    /**
+     * The text size (in pixels) for current {@link TextView}.
+     */
+    private final @Px float mTextSize;
+
+    /**
+     * The LocaleList of the text.
+     */
+    @NonNull private final LocaleList mTextLocales;
+
+    /**
+     * The font family name if the {@link Typeface} of the text is created from a system font
+     * family, otherwise this value should be null.
+     */
+    @Nullable private final String mSystemFontFamilyName;
+
+    /**
+     * The weight of the text.
+     */
+    private final @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int mTextFontWeight;
+
+    /**
+     * The style (normal, bold, italic, bold|italic) of the text, see {@link Typeface}.
+     */
+    private final @Typeface.Style int mTextStyle;
+
+    /**
+     * Whether the transformation method applied to the current {@link TextView} is set to
+     * ALL CAPS.
+     */
+    private final boolean mAllCaps;
+
+    /**
+     * The horizontal offset (in pixels) of the text shadow.
+     */
+    private final @Px float mShadowDx;
+
+    /**
+     * The vertical offset (in pixels) of the text shadow.
+     */
+    private final @Px float mShadowDy;
+
+    /**
+     * The blur radius (in pixels) of the text shadow.
+     */
+    private final @Px float mShadowRadius;
+
+    /**
+     * The elegant text height, especially for less compacted complex script text.
+     */
+    private final boolean mElegantTextHeight;
+
+    /**
+     * Whether to expand linespacing based on fallback fonts.
+     */
+    private final boolean mFallbackLineSpacing;
+
+    /**
+     * The text letter-spacing (in ems), which determines the spacing between characters.
+     */
+    private final float mLetterSpacing;
+
+    /**
+     * The font feature settings.
+     */
+    @Nullable private final String mFontFeatureSettings;
+
+    /**
+     * The font variation settings.
+     */
+    @Nullable private final String mFontVariationSettings;
+
+    /**
+     * The line-break strategies for text wrapping.
+     */
+    private final @LineBreakConfig.LineBreakStyle int mLineBreakStyle;
+
+    /**
+     * The line-break word strategies for text wrapping.
+     */
+    private final @LineBreakConfig.LineBreakWordStyle int mLineBreakWordStyle;
+
+    /**
+     * The extent by which text should be stretched horizontally. Returns 1.0 if not specified.
+     */
+    private final float mTextScaleX;
+
+    /**
+     * The color of the text selection highlight.
+     */
+    private final @ColorInt int mTextColorHighlight;
+
+    /**
+     * The current text color.
+     */
+    private final @ColorInt int mTextColor;
+
+    /**
+     * The current color of the hint text.
+     */
+    private final @ColorInt int mTextColorHint;
+
+    /**
+     * The text color for links.
+     */
+    @Nullable private final ColorStateList mTextColorLink;
+
+    /**
+     * The max length of text.
+     */
+    private final int mMaxLength;
+
+
+    public TextAppearanceInfo(@NonNull TextView textView) {
+        mTextSize = textView.getTextSize();
+        mTextLocales = textView.getTextLocales();
+        Typeface typeface = textView.getPaint().getTypeface();
+        String systemFontFamilyName = null;
+        int textFontWeight = -1;
+        if (typeface != null) {
+            systemFontFamilyName = typeface.getSystemFontFamilyName();
+            textFontWeight = typeface.getWeight();
+        }
+        mSystemFontFamilyName = systemFontFamilyName;
+        mTextFontWeight = textFontWeight;
+        mTextStyle = textView.getTypefaceStyle();
+        mAllCaps = textView.isAllCaps();
+        mShadowRadius = textView.getShadowRadius();
+        mShadowDx = textView.getShadowDx();
+        mShadowDy = textView.getShadowDy();
+        mElegantTextHeight = textView.isElegantTextHeight();
+        mFallbackLineSpacing = textView.isFallbackLineSpacing();
+        mLetterSpacing = textView.getLetterSpacing();
+        mFontFeatureSettings = textView.getFontFeatureSettings();
+        mFontVariationSettings = textView.getFontVariationSettings();
+        mLineBreakStyle = textView.getLineBreakStyle();
+        mLineBreakWordStyle = textView.getLineBreakWordStyle();
+        mTextScaleX = textView.getTextScaleX();
+        mTextColorHighlight = textView.getHighlightColor();
+        mTextColor = textView.getCurrentTextColor();
+        mTextColorHint = textView.getCurrentHintTextColor();
+        mTextColorLink = textView.getLinkTextColors();
+        int maxLength = -1;
+        for (InputFilter filter: textView.getFilters()) {
+            if (filter instanceof InputFilter.LengthFilter) {
+                maxLength = ((InputFilter.LengthFilter) filter).getMax();
+                // There is at most one LengthFilter.
+                break;
+            }
+        }
+        mMaxLength = maxLength;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeFloat(mTextSize);
+        mTextLocales.writeToParcel(dest, flags); // NonNull
+        dest.writeBoolean(mAllCaps);
+        dest.writeString8(mSystemFontFamilyName);
+        dest.writeInt(mTextFontWeight);
+        dest.writeInt(mTextStyle);
+        dest.writeFloat(mShadowDx);
+        dest.writeFloat(mShadowDy);
+        dest.writeFloat(mShadowRadius);
+        dest.writeBoolean(mElegantTextHeight);
+        dest.writeBoolean(mFallbackLineSpacing);
+        dest.writeFloat(mLetterSpacing);
+        dest.writeString8(mFontFeatureSettings);
+        dest.writeString8(mFontVariationSettings);
+        dest.writeInt(mLineBreakStyle);
+        dest.writeInt(mLineBreakWordStyle);
+        dest.writeFloat(mTextScaleX);
+        dest.writeInt(mTextColorHighlight);
+        dest.writeInt(mTextColor);
+        dest.writeInt(mTextColorHint);
+        dest.writeTypedObject(mTextColorLink, flags);
+        dest.writeInt(mMaxLength);
+    }
+
+    private TextAppearanceInfo(@NonNull Parcel in) {
+        mTextSize = in.readFloat();
+        mTextLocales = LocaleList.CREATOR.createFromParcel(in);
+        mAllCaps = in.readBoolean();
+        mSystemFontFamilyName = in.readString8();
+        mTextFontWeight = in.readInt();
+        mTextStyle = in.readInt();
+        mShadowDx = in.readFloat();
+        mShadowDy = in.readFloat();
+        mShadowRadius = in.readFloat();
+        mElegantTextHeight = in.readBoolean();
+        mFallbackLineSpacing = in.readBoolean();
+        mLetterSpacing = in.readFloat();
+        mFontFeatureSettings = in.readString8();
+        mFontVariationSettings = in.readString8();
+        mLineBreakStyle = in.readInt();
+        mLineBreakWordStyle = in.readInt();
+        mTextScaleX = in.readFloat();
+        mTextColorHighlight = in.readInt();
+        mTextColor = in.readInt();
+        mTextColorHint = in.readInt();
+        mTextColorLink = in.readTypedObject(ColorStateList.CREATOR);
+        mMaxLength = in.readInt();
+    }
+
+    @NonNull
+    public static final Creator<TextAppearanceInfo> CREATOR = new Creator<TextAppearanceInfo>() {
+        @Override
+        public TextAppearanceInfo createFromParcel(@NonNull Parcel in) {
+            return new TextAppearanceInfo(in);
+        }
+
+        @Override
+        public TextAppearanceInfo[] newArray(int size) {
+            return new TextAppearanceInfo[size];
+        }
+    };
+
+    /**
+     * Returns the text size (in pixels) for current {@link TextView}.
+     */
+    public @Px float getTextSize() {
+        return mTextSize;
+    }
+
+    /**
+     * Returns the LocaleList of the text.
+     */
+    @NonNull
+    public LocaleList getTextLocales() {
+        return mTextLocales;
+    }
+
+    /**
+     * Returns the font family name if the {@link Typeface} of the text is created from a
+     * system font family. Returns null if no {@link Typeface} is specified, or it is not created
+     * from a system font family.
+     */
+    @Nullable
+    public String getFontFamilyName() {
+        return mSystemFontFamilyName;
+    }
+
+    /**
+     * Returns the weight of the text. Returns -1 when no {@link Typeface} is specified.
+     */
+    public @IntRange(from = -1, to = FontStyle.FONT_WEIGHT_MAX) int getTextFontWeight() {
+        return mTextFontWeight;
+    }
+
+    /**
+     * Returns the style (normal, bold, italic, bold|italic) of the text. Returns
+     * {@link Typeface#NORMAL} when no {@link Typeface} is specified. See {@link Typeface} for
+     * more information.
+     */
+    public @Typeface.Style int getTextStyle() {
+        return mTextStyle;
+    }
+
+    /**
+     * Returns whether the transformation method applied to the current {@link TextView} is set to
+     * ALL CAPS.
+     */
+    public boolean isAllCaps() {
+        return mAllCaps;
+    }
+
+    /**
+     * Returns the horizontal offset (in pixels) of the text shadow.
+     */
+    public @Px float getShadowDx() {
+        return mShadowDx;
+    }
+
+    /**
+     * Returns the vertical offset (in pixels) of the text shadow.
+     */
+    public @Px float getShadowDy() {
+        return mShadowDy;
+    }
+
+    /**
+     * Returns the blur radius (in pixels) of the text shadow.
+     */
+    public @Px float getShadowRadius() {
+        return mShadowRadius;
+    }
+
+    /**
+     * Returns {@code true} if the elegant height metrics flag is set. This setting selects font
+     * variants that have not been compacted to fit Latin-based vertical metrics, and also increases
+     * top and bottom bounds to provide more space.
+     */
+    public boolean isElegantTextHeight() {
+        return mElegantTextHeight;
+    }
+
+    /**
+     * Returns whether to expand linespacing based on fallback fonts.
+     *
+     * @see TextView#setFallbackLineSpacing(boolean)
+     */
+    public boolean isFallbackLineSpacing() {
+        return mFallbackLineSpacing;
+    }
+
+    /**
+     * Returns the text letter-spacing, which determines the spacing between characters.
+     * The value is in 'EM' units. Normally, this value is 0.0.
+     */
+    public float getLetterSpacing() {
+        return mLetterSpacing;
+    }
+
+    /**
+     * Returns the font feature settings. Returns null if not specified.
+     *
+     * @see Paint#getFontFeatureSettings()
+     */
+    @Nullable
+    public String getFontFeatureSettings() {
+        return mFontFeatureSettings;
+    }
+
+    /**
+     * Returns the font variation settings. Returns null if no variation is specified.
+     *
+     * @see Paint#getFontVariationSettings()
+     */
+    @Nullable
+    public String getFontVariationSettings() {
+        return mFontVariationSettings;
+    }
+
+    /**
+     * Returns the line-break strategies for text wrapping.
+     *
+     * @see TextView#setLineBreakStyle(int)
+     */
+    public @LineBreakConfig.LineBreakStyle int getLineBreakStyle() {
+        return mLineBreakStyle;
+    }
+
+    /**
+     * Returns the line-break word strategies for text wrapping.
+     *
+     * @see TextView#setLineBreakWordStyle(int)
+     */
+    public @LineBreakConfig.LineBreakWordStyle int getLineBreakWordStyle() {
+        return mLineBreakWordStyle;
+    }
+
+    /**
+     * Returns the extent by which text should be stretched horizontally. Returns 1.0 if not
+     * specified.
+     */
+    public float getTextScaleX() {
+        return mTextScaleX;
+    }
+
+    /**
+     * Returns the color of the text selection highlight.
+     */
+    public @ColorInt int getTextColorHighlight() {
+        return mTextColorHighlight;
+    }
+
+    /**
+     * Returns the current text color.
+     */
+    public @ColorInt int getTextColor() {
+        return mTextColor;
+    }
+
+    /**
+     * Returns the current color of the hint text.
+     */
+    public @ColorInt int getTextColorHint() {
+        return mTextColorHint;
+    }
+
+    /**
+     * Returns the text color for links.
+     */
+    @Nullable
+    public ColorStateList getTextColorLink() {
+        return mTextColorLink;
+    }
+
+    /**
+     * Returns the max length of text, which is used to set an input filter to constrain the text
+     * length to the specified number. Returns -1 when there is no {@link InputFilter.LengthFilter}
+     * in the Editor.
+     */
+    public int getMaxLength() {
+        return mMaxLength;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof TextAppearanceInfo)) return false;
+        TextAppearanceInfo that = (TextAppearanceInfo) o;
+        return Float.compare(that.mTextSize, mTextSize) == 0
+                && mTextFontWeight == that.mTextFontWeight && mTextStyle == that.mTextStyle
+                && mAllCaps == that.mAllCaps && Float.compare(that.mShadowDx, mShadowDx) == 0
+                && Float.compare(that.mShadowDy, mShadowDy) == 0 && Float.compare(
+                that.mShadowRadius, mShadowRadius) == 0 && mMaxLength == that.mMaxLength
+                && mElegantTextHeight == that.mElegantTextHeight
+                && mFallbackLineSpacing == that.mFallbackLineSpacing && Float.compare(
+                that.mLetterSpacing, mLetterSpacing) == 0 && mLineBreakStyle == that.mLineBreakStyle
+                && mLineBreakWordStyle == that.mLineBreakWordStyle
+                && mTextColorHighlight == that.mTextColorHighlight && mTextColor == that.mTextColor
+                && mTextColorLink.getDefaultColor() == that.mTextColorLink.getDefaultColor()
+                && mTextColorHint == that.mTextColorHint && Objects.equals(
+                mTextLocales, that.mTextLocales) && Objects.equals(mSystemFontFamilyName,
+                that.mSystemFontFamilyName) && Objects.equals(mFontFeatureSettings,
+                that.mFontFeatureSettings) && Objects.equals(mFontVariationSettings,
+                that.mFontVariationSettings) && Float.compare(that.mTextScaleX, mTextScaleX) == 0;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mTextSize, mTextLocales, mSystemFontFamilyName, mTextFontWeight,
+                mTextStyle, mAllCaps, mShadowDx, mShadowDy, mShadowRadius, mElegantTextHeight,
+                mFallbackLineSpacing, mLetterSpacing, mFontFeatureSettings, mFontVariationSettings,
+                mLineBreakStyle, mLineBreakWordStyle, mTextScaleX, mTextColorHighlight, mTextColor,
+                mTextColorHint, mTextColorLink, mMaxLength);
+    }
+
+    @Override
+    public String toString() {
+        return "TextAppearanceInfo{"
+                + "mTextSize=" + mTextSize
+                + ", mTextLocales=" + mTextLocales
+                + ", mSystemFontFamilyName='" + mSystemFontFamilyName + '\''
+                + ", mTextFontWeight=" + mTextFontWeight
+                + ", mTextStyle=" + mTextStyle
+                + ", mAllCaps=" + mAllCaps
+                + ", mShadowDx=" + mShadowDx
+                + ", mShadowDy=" + mShadowDy
+                + ", mShadowRadius=" + mShadowRadius
+                + ", mElegantTextHeight=" + mElegantTextHeight
+                + ", mFallbackLineSpacing=" + mFallbackLineSpacing
+                + ", mLetterSpacing=" + mLetterSpacing
+                + ", mFontFeatureSettings='" + mFontFeatureSettings + '\''
+                + ", mFontVariationSettings='" + mFontVariationSettings + '\''
+                + ", mLineBreakStyle=" + mLineBreakStyle
+                + ", mLineBreakWordStyle=" + mLineBreakWordStyle
+                + ", mTextScaleX=" + mTextScaleX
+                + ", mTextColorHighlight=" + mTextColorHighlight
+                + ", mTextColor=" + mTextColor
+                + ", mTextColorHint=" + mTextColorHint
+                + ", mTextColorLink=" + mTextColorLink
+                + ", mMaxLength=" + mMaxLength
+                + '}';
+    }
+}
diff --git a/core/java/android/view/inputmethod/TextBoundsInfo.java b/core/java/android/view/inputmethod/TextBoundsInfo.java
new file mode 100644
index 0000000..4e87405
--- /dev/null
+++ b/core/java/android/view/inputmethod/TextBoundsInfo.java
@@ -0,0 +1,844 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod;
+
+import android.annotation.IntDef;
+import android.annotation.IntRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Matrix;
+import android.graphics.RectF;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.SegmentFinder;
+
+import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.GrowingArrayUtils;
+import com.android.internal.util.Preconditions;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * The text bounds information of a slice of text in the editor.
+ *
+ * <p> This class provides IME the layout information of the text within the range from
+ * {@link #getStart()} to {@link #getEnd()}. It's intended to be used by IME as a supplementary API
+ * to support handwriting gestures.
+ * </p>
+ */
+public final class TextBoundsInfo implements Parcelable {
+    /**
+     * The flag indicating that the character is a whitespace.
+     *
+     * @see Builder#setCharacterFlags(int[])
+     * @see #getCharacterFlags(int)
+     */
+    public static final int FLAG_CHARACTER_WHITESPACE = 1;
+
+    /**
+     * The flag indicating that the character is a linefeed character.
+     *
+     * @see Builder#setCharacterFlags(int[])
+     * @see #getCharacterFlags(int)
+     */
+    public static final int FLAG_CHARACTER_LINEFEED = 1 << 1;
+
+    /**
+     * The flag indicating that the character is a punctuation.
+     *
+     * @see Builder#setCharacterFlags(int[])
+     * @see #getCharacterFlags(int)
+     */
+    public static final int FLAG_CHARACTER_PUNCTUATION = 1 << 2;
+
+    /**
+     * The flag indicating that the line this character belongs to has RTL line direction. It's
+     * required that all characters in the same line must have the same direction.
+     *
+     * @see Builder#setCharacterFlags(int[])
+     * @see #getCharacterFlags(int)
+     */
+    public static final int FLAG_LINE_IS_RTL = 1 << 3;
+
+
+    /** @hide */
+    @IntDef(prefix = "FLAG_", flag = true, value = {
+            FLAG_CHARACTER_WHITESPACE,
+            FLAG_CHARACTER_LINEFEED,
+            FLAG_CHARACTER_PUNCTUATION,
+            FLAG_LINE_IS_RTL
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface CharacterFlags {}
+
+    /** All the valid flags. */
+    private static final int KNOWN_CHARACTER_FLAGS = FLAG_CHARACTER_WHITESPACE
+            | FLAG_CHARACTER_LINEFEED | FLAG_CHARACTER_PUNCTUATION  | FLAG_LINE_IS_RTL;
+
+    /**
+     * The amount of shift to get the character's BiDi level from the internal character flags.
+     */
+    private static final int BIDI_LEVEL_SHIFT = 19;
+
+    /**
+     * The mask used to get the character's BiDi level from the internal character flags.
+     */
+    private static final int BIDI_LEVEL_MASK = 0x7F << BIDI_LEVEL_SHIFT;
+
+    /**
+     * The flag indicating that the character at the index is the start of a line segment.
+     * This flag is only used internally to serialize the {@link SegmentFinder}.
+     *
+     * @see #writeToParcel(Parcel, int)
+     */
+    private static final int FLAG_LINE_SEGMENT_START = 1 << 31;
+
+    /**
+     * The flag indicating that the character at the index is the end of a line segment.
+     * This flag is only used internally to serialize the {@link SegmentFinder}.
+     *
+     * @see #writeToParcel(Parcel, int)
+     */
+    private static final int FLAG_LINE_SEGMENT_END = 1 << 30;
+
+    /**
+     * The flag indicating that the character at the index is the start of a word segment.
+     * This flag is only used internally to serialize the {@link SegmentFinder}.
+     *
+     * @see #writeToParcel(Parcel, int)
+     */
+    private static final int FLAG_WORD_SEGMENT_START = 1 << 29;
+
+    /**
+     * The flag indicating that the character at the index is the end of a word segment.
+     * This flag is only used internally to serialize the {@link SegmentFinder}.
+     *
+     * @see #writeToParcel(Parcel, int)
+     */
+    private static final int FLAG_WORD_SEGMENT_END = 1 << 28;
+
+    /**
+     * The flag indicating that the character at the index is the start of a grapheme segment.
+     * It's only used internally to serialize the {@link SegmentFinder}.
+     *
+     * @see #writeToParcel(Parcel, int)
+     */
+    private static final int FLAG_GRAPHEME_SEGMENT_START = 1 << 27;
+
+    /**
+     * The flag indicating that the character at the index is the end of a grapheme segment.
+     * It's only used internally to serialize the {@link SegmentFinder}.
+     *
+     * @see #writeToParcel(Parcel, int)
+     */
+    private static final int FLAG_GRAPHEME_SEGMENT_END = 1 << 26;
+
+    private final int mStart;
+    private final int mEnd;
+    private final float[] mMatrixValues;
+    private final float[] mCharacterBounds;
+    /**
+     * The array that encodes character and BiDi levels. They are stored together to save memory
+     * space, and it's easier during serialization.
+     */
+    private final int[] mInternalCharacterFlags;
+    private final SegmentFinder mLineSegmentFinder;
+    private final SegmentFinder mWordSegmentFinder;
+    private final SegmentFinder mGraphemeSegmentFinder;
+
+    /**
+     * Returns a new instance of {@link android.graphics.Matrix} that indicates the transformation
+     * matrix that is to be applied other positional data in this class.
+     *
+     * @return a new instance (copy) of the transformation matrix.
+     */
+    @NonNull
+    public Matrix getMatrix() {
+        final Matrix matrix = new Matrix();
+        matrix.setValues(mMatrixValues);
+        return matrix;
+    }
+
+    /**
+     * Returns the index of the first character whose bounds information is available in this
+     * {@link TextBoundsInfo}, inclusive.
+     *
+     * @see Builder#setStartAndEnd(int, int)
+     */
+    public int getStart() {
+        return mStart;
+    }
+
+    /**
+     * Returns the index of the last character whose bounds information is available in this
+     * {@link TextBoundsInfo}, exclusive.
+     *
+     * @see Builder#setStartAndEnd(int, int)
+     */
+    public int getEnd() {
+        return mEnd;
+    }
+
+    /**
+     * Return the bounds of the character at the given {@code index}, in the coordinates of the
+     * editor.
+     *
+     * @param index the index of the queried character.
+     * @return the bounding box of the queried character.
+     *
+     * @throws IndexOutOfBoundsException if the given {@code index} is out of the range from
+     * the {@code start} to the {@code end}.
+     */
+    @NonNull
+    public RectF getCharacterBounds(int index) {
+        if (index < mStart || index >= mEnd) {
+            throw new IndexOutOfBoundsException("Index is out of the bounds of "
+                    + "[" + mStart + ", " + mEnd + ").");
+        }
+        final int offset = 4 * (index - mStart);
+        return new RectF(mCharacterBounds[offset], mCharacterBounds[offset + 1],
+                mCharacterBounds[offset + 2], mCharacterBounds[offset + 3]);
+    }
+
+    /**
+     * Return the flags associated with the character at the given {@code index}.
+     * The flags contain the following information:
+     * <ul>
+     *     <li>The {@link #FLAG_CHARACTER_WHITESPACE} flag, indicating the character is a
+     *     whitespace. </li>
+     *     <li>The {@link #FLAG_CHARACTER_LINEFEED} flag, indicating the character is a
+     *     linefeed. </li>
+     *     <li>The {@link #FLAG_CHARACTER_PUNCTUATION} flag, indicating the character is a
+     *     punctuation. </li>
+     *     <li>The {@link #FLAG_LINE_IS_RTL} flag, indicating the line this character belongs to
+     *     has RTL line direction. All characters in the same line must have the same line
+     *     direction. Check {@link #getLineSegmentFinder()} for more information of
+     *     line boundaries. </li>
+     * </ul>
+     *
+     * @param index the index of the queried character.
+     * @return the flags associated with the queried character.
+     *
+     * @throws IndexOutOfBoundsException if the given {@code index} is out of the range from
+     * the {@code start} to the {@code end}.
+     *
+     * @see #FLAG_CHARACTER_WHITESPACE
+     * @see #FLAG_CHARACTER_LINEFEED
+     * @see #FLAG_CHARACTER_PUNCTUATION
+     * @see #FLAG_LINE_IS_RTL
+     */
+    @CharacterFlags
+    public int getCharacterFlags(int index) {
+        if (index < mStart || index >= mEnd) {
+            throw new IndexOutOfBoundsException("Index is out of the bounds of "
+                    + "[" + mStart + ", " + mEnd + ").");
+        }
+        final int offset = index - mStart;
+        return mInternalCharacterFlags[offset] & KNOWN_CHARACTER_FLAGS;
+    }
+
+    /**
+     * The BiDi level of the character at the given {@code index}. <br/>
+     * BiDi level is defined by
+     * <a href="https://unicode.org/reports/tr9/#Basic_Display_Algorithm" >the unicode
+     * bidirectional algorithm </a>. One can determine whether a character's direction is
+     * right-to-left (RTL) or left-to-right (LTR) by checking the last bit of the BiDi level.
+     * If it's 1, the character is RTL, otherwise the character is LTR. The BiDi level of a
+     * character must be in the range of [0, 125].
+     *
+     * @param index the index of the queried character.
+     * @return the BiDi level of the character, which is an integer in the range of [0, 125].
+     * @throws IndexOutOfBoundsException if the given {@code index} is out of the range from
+     * the {@code start} to the {@code end}.
+     *
+     * @see Builder#setCharacterBidiLevel(int[])
+     */
+    @IntRange(from = 0, to = 125)
+    public int getCharacterBidiLevel(int index) {
+        if (index < mStart || index >= mEnd) {
+            throw new IndexOutOfBoundsException("Index is out of the bounds of "
+                    + "[" + mStart + ", " + mEnd + ").");
+        }
+        final int offset = index - mStart;
+        return (mInternalCharacterFlags[offset] & BIDI_LEVEL_MASK) >> BIDI_LEVEL_SHIFT;
+    }
+
+    /**
+     * Returns the {@link SegmentFinder} that locates the word boundaries.
+     *
+     * @see Builder#setWordSegmentFinder(SegmentFinder)
+     */
+    @NonNull
+    public SegmentFinder getWordSegmentFinder() {
+        return mWordSegmentFinder;
+    }
+
+    /**
+     * Returns the {@link SegmentFinder} that locates the grapheme boundaries.
+     *
+     * @see Builder#setGraphemeSegmentFinder(SegmentFinder)
+     */
+    @NonNull
+    public SegmentFinder getGraphemeSegmentFinder() {
+        return mGraphemeSegmentFinder;
+    }
+
+    /**
+     * Returns the {@link SegmentFinder} that locates the line boundaries.
+     *
+     * @see Builder#setLineSegmentFinder(SegmentFinder)
+     */
+    @NonNull
+    public SegmentFinder getLineSegmentFinder() {
+        return mLineSegmentFinder;
+    }
+
+    /**
+     * Describe the kinds of special objects contained in this Parcelable
+     * instance's marshaled representation. For example, if the object will
+     * include a file descriptor in the output of {@link #writeToParcel(Parcel, int)},
+     * the return value of this method must include the
+     * {@link #CONTENTS_FILE_DESCRIPTOR} bit.
+     *
+     * @return a bitmask indicating the set of special object types marshaled
+     * by this Parcelable object instance.
+     */
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Flatten this object in to a Parcel.
+     *
+     * @param dest  The Parcel in which the object should be written.
+     * @param flags Additional flags about how the object should be written.
+     *              May be 0 or {@link #PARCELABLE_WRITE_RETURN_VALUE}.
+     */
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeInt(mStart);
+        dest.writeInt(mEnd);
+        dest.writeFloatArray(mMatrixValues);
+        dest.writeFloatArray(mCharacterBounds);
+
+        // The end can also be a break position. We need an extra space to encode the breaks.
+        final int[] encodedFlags = Arrays.copyOf(mInternalCharacterFlags, mEnd - mStart + 1);
+        encodeSegmentFinder(encodedFlags, FLAG_GRAPHEME_SEGMENT_START, FLAG_GRAPHEME_SEGMENT_END,
+                mStart, mEnd, mGraphemeSegmentFinder);
+        encodeSegmentFinder(encodedFlags, FLAG_WORD_SEGMENT_START, FLAG_WORD_SEGMENT_END, mStart,
+                mEnd, mWordSegmentFinder);
+        encodeSegmentFinder(encodedFlags, FLAG_LINE_SEGMENT_START, FLAG_LINE_SEGMENT_END, mStart,
+                mEnd, mLineSegmentFinder);
+        dest.writeIntArray(encodedFlags);
+    }
+
+    private TextBoundsInfo(Parcel source) {
+        mStart = source.readInt();
+        mEnd  = source.readInt();
+        mMatrixValues = Objects.requireNonNull(source.createFloatArray());
+        mCharacterBounds = Objects.requireNonNull(source.createFloatArray());
+        final int[] encodedFlags = Objects.requireNonNull(source.createIntArray());
+
+        mGraphemeSegmentFinder = decodeSegmentFinder(encodedFlags, FLAG_GRAPHEME_SEGMENT_START,
+                FLAG_GRAPHEME_SEGMENT_END, mStart, mEnd);
+        mWordSegmentFinder = decodeSegmentFinder(encodedFlags, FLAG_WORD_SEGMENT_START,
+                FLAG_WORD_SEGMENT_END, mStart, mEnd);
+        mLineSegmentFinder = decodeSegmentFinder(encodedFlags, FLAG_LINE_SEGMENT_START,
+                FLAG_LINE_SEGMENT_END, mStart, mEnd);
+
+        final int length = mEnd - mStart;
+        final int flagsMask = KNOWN_CHARACTER_FLAGS | BIDI_LEVEL_MASK;
+        mInternalCharacterFlags = new int[length];
+        for (int i = 0; i < length; ++i) {
+            // Remove the flags used to encoded segment boundaries.
+            mInternalCharacterFlags[i] = encodedFlags[i] & flagsMask;
+        }
+    }
+
+    private TextBoundsInfo(Builder builder) {
+        mStart = builder.mStart;
+        mEnd = builder.mEnd;
+        mMatrixValues = Arrays.copyOf(builder.mMatrixValues, 9);
+        final int length = mEnd - mStart;
+        mCharacterBounds = Arrays.copyOf(builder.mCharacterBounds, 4 * length);
+        // Store characterFlags and characterBidiLevels to save memory.
+        mInternalCharacterFlags = new int[length];
+        for (int index = 0; index < length; ++index) {
+            mInternalCharacterFlags[index] = builder.mCharacterFlags[index]
+                    | (builder.mCharacterBidiLevels[index] << BIDI_LEVEL_SHIFT);
+        }
+        mGraphemeSegmentFinder = builder.mGraphemeSegmentFinder;
+        mWordSegmentFinder = builder.mWordSegmentFinder;
+        mLineSegmentFinder = builder.mLineSegmentFinder;
+    }
+
+    /**
+     * The CREATOR to make this class Parcelable.
+     */
+    @NonNull
+    public static final Parcelable.Creator<TextBoundsInfo> CREATOR = new Creator<TextBoundsInfo>() {
+        @Override
+        public TextBoundsInfo createFromParcel(Parcel source) {
+            return new TextBoundsInfo(source);
+        }
+
+        @Override
+        public TextBoundsInfo[] newArray(int size) {
+            return new TextBoundsInfo[size];
+        }
+    };
+
+    private static final String TEXT_BOUNDS_INFO_KEY = "android.view.inputmethod.TextBoundsInfo";
+
+    /**
+     * Store the {@link TextBoundsInfo} into a {@link Bundle}. This method is used by
+     * {@link RemoteInputConnectionImpl} to transfer the {@link TextBoundsInfo} from the editor
+     * to IME.
+     *
+     * @see TextBoundsInfoResult
+     * @see InputConnection#requestTextBoundsInfo(RectF, Executor, Consumer)
+     * @hide
+     */
+    @NonNull
+    public Bundle toBundle() {
+        final Bundle bundle = new Bundle();
+        bundle.putParcelable(TEXT_BOUNDS_INFO_KEY, this);
+        return bundle;
+
+    }
+
+    /** @hide */
+    @Nullable
+    public static TextBoundsInfo createFromBundle(@Nullable Bundle bundle) {
+        if (bundle == null) return null;
+        return bundle.getParcelable(TEXT_BOUNDS_INFO_KEY, TextBoundsInfo.class);
+    }
+
+    /**
+     * The builder class to create a {@link TextBoundsInfo} object.
+     */
+    public static final class Builder {
+        private final float[] mMatrixValues = new float[9];
+        private boolean mMatrixInitialized;
+        private int mStart;
+        private int mEnd;
+        private float[] mCharacterBounds;
+        private int[] mCharacterFlags;
+        private int[] mCharacterBidiLevels;
+        private SegmentFinder mLineSegmentFinder;
+        private SegmentFinder mWordSegmentFinder;
+        private SegmentFinder mGraphemeSegmentFinder;
+
+        /** Clear all the parameters set on this {@link Builder} to reuse it. */
+        @NonNull
+        public Builder clear() {
+            mMatrixInitialized = false;
+            mStart = -1;
+            mEnd = -1;
+            mCharacterBounds = null;
+            mCharacterFlags = null;
+            mLineSegmentFinder = null;
+            mWordSegmentFinder = null;
+            mGraphemeSegmentFinder = null;
+            return this;
+        }
+
+        /**
+         * Sets the matrix that transforms local coordinates into screen coordinates.
+         *
+         * @param matrix transformation matrix from local coordinates into screen coordinates.
+         * @throws NullPointerException if the given {@code matrix} is {@code null}.
+         */
+        @NonNull
+        public Builder setMatrix(@NonNull Matrix matrix) {
+            Objects.requireNonNull(matrix).getValues(mMatrixValues);
+            mMatrixInitialized = true;
+            return this;
+        }
+
+        /**
+         * Set the start and end index of the {@link TextBoundsInfo}. It's the range of the
+         * characters whose information is available in the {@link TextBoundsInfo}.
+         *
+         * @param start the start index of the {@link TextBoundsInfo}, inclusive.
+         * @param end the end index of the {@link TextBoundsInfo}, exclusive.
+         * @throws IllegalArgumentException if the given {@code start} or {@code end} is negative,
+         * or {@code end} is smaller than the {@code start}.
+         */
+        @NonNull
+        @SuppressWarnings("MissingGetterMatchingBuilder")
+        public Builder setStartAndEnd(@IntRange(from = 0) int start, @IntRange(from = 0) int end) {
+            Preconditions.checkArgument(start >= 0);
+            Preconditions.checkArgumentInRange(start, 0, end, "start");
+            mStart = start;
+            mEnd = end;
+            return this;
+        }
+
+        /**
+         * Set the characters bounds, in the coordinates of the editor. <br/>
+         *
+         * The given array should be divided into groups of four where each element represents
+         * left, top, right and bottom of the character bounds respectively.
+         * The bounds of the i-th character in the editor should be stored at index
+         * 4 * (i - start). The length of the given array must equal to 4 * (end - start). <br/>
+         *
+         * Sometimes multiple characters in a single grapheme are rendered as one symbol on the
+         * screen. So those characters only have one shared bounds. In this case, we recommend the
+         * editor to assign all the width to the bounds of the first character in the grapheme,
+         * and make the rest characters' bounds zero-width. <br/>
+         *
+         * For example, the string "'0xD83D' '0xDE00'" is rendered as one grapheme - a grinning face
+         * emoji. If the bounds of the grapheme is: Rect(5, 10, 15, 20), the character bounds of the
+         * string should be: [ Rect(5, 10, 15, 20), Rect(15, 10, 15, 20) ].
+         *
+         * @param characterBounds the array of the flattened character bounds.
+         * @throws NullPointerException if the given {@code characterBounds} is {@code null}.
+         */
+        @NonNull
+        public Builder setCharacterBounds(@NonNull float[] characterBounds) {
+            mCharacterBounds = Objects.requireNonNull(characterBounds);
+            return this;
+        }
+
+        /**
+         * Set the flags of the characters. The flags of the i-th character in the editor is stored
+         * at index (i - start). The length of the given array must equal to (end - start).
+         * The flags contain the following information:
+         * <ul>
+         *     <li>The {@link #FLAG_CHARACTER_WHITESPACE} flag, indicating the character is a
+         *     whitespace. </li>
+         *     <li>The {@link #FLAG_CHARACTER_LINEFEED} flag, indicating the character is a
+         *     linefeed. </li>
+         *     <li>The {@link #FLAG_CHARACTER_PUNCTUATION} flag, indicating the character is a
+         *     punctuation. </li>
+         *     <li>The {@link #FLAG_LINE_IS_RTL} flag, indicating the line this character belongs to
+         *     is RTL. All all character in the same line must have the same line direction. Check
+         *     {@link #getLineSegmentFinder()} for more information of line boundaries. </li>
+         * </ul>
+         *
+         * @param characterFlags the array of the character's flags.
+         * @throws NullPointerException if the given {@code characterFlags} is {@code null}.
+         * @throws IllegalArgumentException if the given {@code characterFlags} contains invalid
+         * flags.
+         *
+         * @see #getCharacterFlags(int)
+         */
+        @NonNull
+        public Builder setCharacterFlags(@NonNull int[] characterFlags) {
+            Objects.requireNonNull(characterFlags);
+            for (int characterFlag : characterFlags) {
+                if ((characterFlag & (~KNOWN_CHARACTER_FLAGS)) != 0) {
+                    throw new IllegalArgumentException("characterFlags contains invalid flags.");
+                }
+            }
+            mCharacterFlags = characterFlags;
+            return this;
+        }
+
+        /**
+         * Set the BiDi levels for the character. The bidiLevel of the i-th character in the editor
+         * is stored at index (i - start). The length of the given array must equal to
+         * (end - start). <br/>
+         *
+         * BiDi level is defined by
+         * <a href="https://unicode.org/reports/tr9/#Basic_Display_Algorithm" >the unicode
+         * bidirectional algorithm </a>. One can determine whether a character's direction is
+         * right-to-left (RTL) or left-to-right (LTR) by checking the last bit of the BiDi level.
+         * If it's 1, the character is RTL, otherwise the character is LTR. The BiDi level of a
+         * character must be in the range of [0, 125].
+         * @param characterBidiLevels the array of the character's BiDi level.
+         *
+         * @throws NullPointerException if the given {@code characterBidiLevels} is {@code null}.
+         * @throws IllegalArgumentException if the given {@code characterBidiLevels} contains an
+         * element that's out of the range [0, 125].
+         *
+         * @see #getCharacterBidiLevel(int)
+         */
+        @NonNull
+        public Builder setCharacterBidiLevel(@NonNull int[] characterBidiLevels) {
+            Objects.requireNonNull(characterBidiLevels);
+            for (int index = 0; index < characterBidiLevels.length; ++index) {
+                Preconditions.checkArgumentInRange(characterBidiLevels[index], 0, 125,
+                        "bidiLevels[" + index + "]");
+            }
+            mCharacterBidiLevels = characterBidiLevels;
+            return this;
+        }
+
+        /**
+         * Set the {@link SegmentFinder} that locates the grapheme cluster boundaries. Grapheme is
+         * defined in <a href="https://unicode.org/reports/tr29/#Grapheme_Cluster_Boundaries">
+         * the unicode annex #29: unicode text segmentation<a/>. It's a user-perspective character.
+         * And it's usually the minimal unit for selection, backspace, deletion etc. <br/>
+         *
+         * Please note that only the grapheme segments within the range from start to end will
+         * be available to the IME. The remaining information will be discarded during serialization
+         * for better performance.
+         *
+         * @param graphemeSegmentFinder the {@link SegmentFinder} that locates the grapheme cluster
+         *                              boundaries.
+         * @throws NullPointerException if the given {@code graphemeSegmentFinder} is {@code null}.
+         *
+         * @see #getGraphemeSegmentFinder()
+         * @see SegmentFinder
+         * @see SegmentFinder.DefaultSegmentFinder
+         */
+        @NonNull
+        public Builder setGraphemeSegmentFinder(@NonNull SegmentFinder graphemeSegmentFinder) {
+            mGraphemeSegmentFinder = Objects.requireNonNull(graphemeSegmentFinder);
+            return this;
+        }
+
+        /**
+         * Set the {@link SegmentFinder} that locates the word boundaries. <br/>
+         *
+         * Please note that only the word segments within the range from start to end will
+         * be available to the IME. The remaining information will be discarded during serialization
+         * for better performance.
+         * @param wordSegmentFinder set the {@link SegmentFinder} that locates the word boundaries.
+         * @throws NullPointerException if the given {@code wordSegmentFinder} is {@code null}.
+         *
+         * @see #getWordSegmentFinder()
+         * @see SegmentFinder
+         * @see SegmentFinder.DefaultSegmentFinder
+         */
+        @NonNull
+        public Builder setWordSegmentFinder(@NonNull SegmentFinder wordSegmentFinder) {
+            mWordSegmentFinder = Objects.requireNonNull(wordSegmentFinder);
+            return this;
+        }
+
+        /**
+         * Set the {@link SegmentFinder} that locates the line boundaries. Aside from the hard
+         * breaks in the text, it should also locate the soft line breaks added by the editor.
+         * It is expected that the characters within the same line is rendered on the same baseline.
+         * (Except for some text formatted as subscript and superscript.) <br/>
+         *
+         * Please note that only the line segments within the range from start to end will
+         * be available to the IME. The remaining information will be discarded during serialization
+         * for better performance.
+         * @param lineSegmentFinder set the {@link SegmentFinder} that locates the line boundaries.
+         * @throws NullPointerException if the given {@code lineSegmentFinder} is {@code null}.
+         *
+         * @see #getLineSegmentFinder()
+         * @see SegmentFinder
+         * @see SegmentFinder.DefaultSegmentFinder
+         */
+        @NonNull
+        public Builder setLineSegmentFinder(@NonNull SegmentFinder lineSegmentFinder) {
+            mLineSegmentFinder = Objects.requireNonNull(lineSegmentFinder);
+            return this;
+        }
+
+        /**
+         * Create the {@link TextBoundsInfo} using the parameters in this {@link Builder}.
+         *
+         * @throws IllegalStateException in the following conditions:
+         * <ul>
+         *     <li>if the {@code start} or {@code end} is not set.</li>
+         *     <li>if the {@code matrix} is not set.</li>
+         *     <li>if {@code characterBounds} is not set or its length doesn't equal to
+         *     4 * ({@code end} - {@code start}).</li>
+         *     <li>if the {@code characterFlags} is not set or its length doesn't equal to
+         *     ({@code end} - {@code start}).</li>
+         *     <li>if {@code graphemeSegmentFinder}, {@code wordSegmentFinder} or
+         *     {@code lineSegmentFinder} is not set.</li>
+         *     <li>if characters in the same line has inconsistent {@link #FLAG_LINE_IS_RTL}
+         *     flag.</li>
+         * </ul>
+         */
+        @NonNull
+        public TextBoundsInfo build() {
+            if (mStart < 0 || mEnd < 0) {
+                throw new IllegalStateException("Start and end must be set.");
+            }
+
+            if (!mMatrixInitialized) {
+                throw new IllegalStateException("Matrix must be set.");
+            }
+
+            if (mCharacterBounds == null) {
+                throw new IllegalStateException("CharacterBounds must be set.");
+            }
+
+            if (mCharacterFlags == null) {
+                throw new IllegalStateException("CharacterFlags must be set.");
+            }
+
+            if (mCharacterBidiLevels == null) {
+                throw new IllegalStateException("CharacterBidiLevel must be set.");
+            }
+
+            if (mCharacterBounds.length != 4 * (mEnd - mStart)) {
+                throw new IllegalStateException("The length of characterBounds doesn't match the "
+                        + "length of the given start and end."
+                        + " Expected length: " + (4 * (mEnd - mStart))
+                        + " characterBounds length: " + mCharacterBounds.length);
+            }
+            if (mCharacterFlags.length != mEnd - mStart) {
+                throw new IllegalStateException("The length of characterFlags doesn't match the "
+                        + "length of the given start and end."
+                        + " Expected length: " + (mEnd - mStart)
+                        + " characterFlags length: " + mCharacterFlags.length);
+            }
+            if (mCharacterBidiLevels.length != mEnd - mStart) {
+                throw new IllegalStateException("The length of characterBidiLevels doesn't match"
+                        + " the length of the given start and end."
+                        + " Expected length: " + (mEnd - mStart)
+                        + " characterFlags length: " + mCharacterBidiLevels.length);
+            }
+            if (mGraphemeSegmentFinder == null) {
+                throw new IllegalStateException("GraphemeSegmentFinder must be set.");
+            }
+            if (mWordSegmentFinder == null) {
+                throw new IllegalStateException("WordSegmentFinder must be set.");
+            }
+            if (mLineSegmentFinder == null) {
+                throw new IllegalStateException("LineSegmentFinder must be set.");
+            }
+
+            if (!isLineDirectionFlagConsistent(mCharacterFlags, mLineSegmentFinder, mStart, mEnd)) {
+                throw new IllegalStateException("characters in the same line must have the same "
+                        + "FLAG_LINE_IS_RTL flag value.");
+            }
+            return new TextBoundsInfo(this);
+        }
+    }
+
+    /**
+     * Encode the segment start and end positions in {@link SegmentFinder} to a flags array.
+     *
+     * For example:
+     * Text: "A BC DE"
+     * Input:
+     *     start: 2, end: 7                                     // substring "BC DE"
+     *     SegmentFinder: segment ranges = [(2, 4), (5, 7)]     // a word break iterator
+     *     flags: [0x0000, 0x0000, 0x0080, 0x0000, 0x0000, 0x0000] // 0x0080 is whitespace
+     *     segmentStartFlag: 0x0100
+     *     segmentEndFlag: 0x0200
+     * Output:
+     *     flags: [0x0100, 0x0000, 0x0280, 0x0100, 0x0000, 0x0200]
+     *  The index 2 and 5 encode segment starts, the index 4 and 7 encode a segment end.
+     *
+     * @param flags the flags array to receive the results.
+     * @param segmentStartFlag the flag used to encode the segment start.
+     * @param segmentEndFlag the flag used to encode the segment end.
+     * @param start the start index of the encoded range, inclusive.
+     * @param end the end index of the encoded range, inclusive.
+     * @param segmentFinder the SegmentFinder to be encoded.
+     *
+     * @see #decodeSegmentFinder(int[], int, int, int, int)
+     */
+    private static void encodeSegmentFinder(@NonNull int[] flags, int segmentStartFlag,
+            int segmentEndFlag, int start, int end, @NonNull SegmentFinder segmentFinder) {
+        if (end - start + 1 != flags.length) {
+            throw new IllegalStateException("The given flags array must have the same length as"
+                    + " the given range. flags length: " + flags.length
+                    + " range: [" + start + ", " + end + "]");
+        }
+
+        int segmentEnd = segmentFinder.nextEndBoundary(start);
+        if (segmentEnd == SegmentFinder.DONE) return;
+        int segmentStart = segmentFinder.previousStartBoundary(segmentEnd);
+
+        while (segmentEnd != SegmentFinder.DONE && segmentEnd <= end) {
+            if (segmentStart >= start) {
+                flags[segmentStart - start] |= segmentStartFlag;
+                flags[segmentEnd - start] |= segmentEndFlag;
+            }
+            segmentStart = segmentFinder.nextStartBoundary(segmentStart);
+            segmentEnd = segmentFinder.nextEndBoundary(segmentEnd);
+        }
+    }
+
+    /**
+     * Decode a {@link SegmentFinder} from a flags array.
+     *
+     * For example:
+     * Text: "A BC DE"
+     * Input:
+     *     start: 2, end: 7                                     // substring "BC DE"
+     *     flags: [0x0100, 0x0000, 0x0280, 0x0100, 0x0000, 0x0200]
+     *     segmentStartFlag: 0x0100
+     *     segmentEndFlag: 0x0200
+     * Output:
+     *     SegmentFinder: segment ranges = [(2, 4), (5, 7)]
+     *
+     * @param flags the flags array to decode the SegmentFinder.
+     * @param segmentStartFlag the flag to decode a segment start.
+     * @param segmentEndFlag the flag to decode a segment end.
+     * @param start the start index of the interested range, inclusive.
+     * @param end the end index of the interested range, inclusive.
+     *
+     * @see #encodeSegmentFinder(int[], int, int, int, int, SegmentFinder)
+     */
+    private static SegmentFinder decodeSegmentFinder(int[] flags, int segmentStartFlag,
+            int segmentEndFlag, int start, int end) {
+        if (end - start + 1 != flags.length) {
+            throw new IllegalStateException("The given flags array must have the same length as"
+                    + " the given range. flags length: " + flags.length
+                    + " range: [" + start + ", " + end + "]");
+        }
+        int[] breaks = ArrayUtils.newUnpaddedIntArray(10);
+        int count = 0;
+        for (int offset = 0; offset < flags.length; ++offset) {
+            if ((flags[offset] & segmentStartFlag) == segmentStartFlag) {
+                breaks = GrowingArrayUtils.append(breaks, count++, start + offset);
+            }
+            if ((flags[offset] & segmentEndFlag) == segmentEndFlag) {
+                breaks = GrowingArrayUtils.append(breaks, count++, start + offset);
+            }
+        }
+        return new SegmentFinder.DefaultSegmentFinder(Arrays.copyOf(breaks, count));
+    }
+
+    /**
+     * Check whether the {@link #FLAG_LINE_IS_RTL} is the same for characters in the same line.
+     * @return true if all characters in the same line has the same {@link #FLAG_LINE_IS_RTL} flag.
+     */
+    private static boolean isLineDirectionFlagConsistent(int[] characterFlags,
+            SegmentFinder lineSegmentFinder, int start, int end) {
+        int segmentEnd = lineSegmentFinder.nextEndBoundary(start);
+        if (segmentEnd == SegmentFinder.DONE) return true;
+        int segmentStart = lineSegmentFinder.previousStartBoundary(segmentEnd);
+
+        while (segmentStart != SegmentFinder.DONE && segmentStart < end) {
+            final int lineStart = Math.max(segmentStart, start);
+            final int lineEnd = Math.min(segmentEnd, end);
+            final boolean lineIsRtl = (characterFlags[lineStart - start] & FLAG_LINE_IS_RTL) != 0;
+            for (int index = lineStart + 1; index < lineEnd; ++index) {
+                final int flags = characterFlags[index - start];
+                final boolean characterLineIsRtl = (flags & FLAG_LINE_IS_RTL) != 0;
+                if (characterLineIsRtl != lineIsRtl) {
+                    return false;
+                }
+            }
+
+            segmentStart = lineSegmentFinder.nextStartBoundary(segmentStart);
+            segmentEnd = lineSegmentFinder.nextEndBoundary(segmentEnd);
+        }
+        return true;
+    }
+}
diff --git a/core/java/android/view/inputmethod/TextBoundsInfoResult.java b/core/java/android/view/inputmethod/TextBoundsInfoResult.java
new file mode 100644
index 0000000..62df17a
--- /dev/null
+++ b/core/java/android/view/inputmethod/TextBoundsInfoResult.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.RectF;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * The object that holds the result of the
+ * {@link InputConnection#requestTextBoundsInfo(RectF, Executor, Consumer)} call.
+ *
+ * @see InputConnection#requestTextBoundsInfo(RectF, Executor, Consumer)
+ */
+public final class TextBoundsInfoResult {
+    private final int mResultCode;
+    private final TextBoundsInfo mTextBoundsInfo;
+
+    /**
+     * Result for {@link InputConnection#requestTextBoundsInfo(RectF, Executor, Consumer)} when the
+     * editor doesn't implement the method.
+     */
+    public static final int CODE_UNSUPPORTED = 0;
+
+    /**
+     * Result for {@link InputConnection#requestTextBoundsInfo(RectF, Executor, Consumer)} when the
+     * editor successfully returns a {@link TextBoundsInfo}.
+     */
+    public static final int CODE_SUCCESS = 1;
+
+    /**
+     * Result for {@link InputConnection#requestTextBoundsInfo(RectF, Executor, Consumer)} when the
+     * request failed. This result code is returned when the editor can't provide a valid
+     * {@link TextBoundsInfo}. (e.g. The editor view is not laid out.)
+     */
+    public static final int CODE_FAILED = 2;
+
+    /**
+     * Result for {@link InputConnection#requestTextBoundsInfo(RectF, Executor, Consumer)} when the
+     * request is cancelled. This happens when the {@link InputConnection} is or becomes
+     * invalidated while requesting the
+     * {@link TextBoundsInfo}, for example because a new {@code InputConnection} was started, or
+     * due to {@link InputMethodManager#invalidateInput}.
+     */
+    public static final int CODE_CANCELLED = 3;
+
+    /** @hide */
+    @IntDef(prefix = { "CODE_" }, value = {
+            CODE_UNSUPPORTED,
+            CODE_SUCCESS,
+            CODE_FAILED,
+            CODE_CANCELLED,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ResultCode {}
+
+    /**
+     * Create a {@link TextBoundsInfoResult} object with no {@link TextBoundsInfo}.
+     * The given {@code resultCode} can't be {@link #CODE_SUCCESS}.
+     * @param resultCode the result code of the
+     * {@link InputConnection#requestTextBoundsInfo(RectF, Executor, Consumer)} call.
+     */
+    public TextBoundsInfoResult(@ResultCode int resultCode) {
+        this(resultCode, null);
+    }
+
+    /**
+     * Create a {@link TextBoundsInfoResult} object.
+     *
+     * @param resultCode the result code of the
+     * {@link InputConnection#requestTextBoundsInfo(RectF, Executor, Consumer)} call.
+     * @param textBoundsInfo the returned {@link TextBoundsInfo} of the
+     * {@link InputConnection#requestTextBoundsInfo(RectF, Executor, Consumer)} call. It can't be
+     *                       null if the {@code resultCode} is {@link #CODE_SUCCESS}.
+     *
+     * @throws IllegalStateException if the resultCode is
+     * {@link #CODE_SUCCESS} but the given {@code textBoundsInfo}
+     * is null.
+     */
+    public TextBoundsInfoResult(@ResultCode int resultCode,
+            @NonNull TextBoundsInfo textBoundsInfo) {
+        if (resultCode == CODE_SUCCESS && textBoundsInfo == null) {
+            throw new IllegalStateException("TextBoundsInfo must be provided when the resultCode "
+                    + "is CODE_SUCCESS.");
+        }
+        mResultCode = resultCode;
+        mTextBoundsInfo = textBoundsInfo;
+    }
+
+    /**
+     * Return the result code of the
+     * {@link InputConnection#requestTextBoundsInfo(RectF, Executor, Consumer)} call.
+     * Its value is one of the {@link #CODE_UNSUPPORTED}, {@link #CODE_SUCCESS},
+     * {@link #CODE_FAILED} and {@link #CODE_CANCELLED}.
+     */
+    @ResultCode
+    public int getResultCode() {
+        return mResultCode;
+    }
+
+    /**
+     * Return the {@link TextBoundsInfo} provided by the editor. It is non-null if the
+     * {@code resultCode} is {@link #CODE_SUCCESS}.
+     * Otherwise, it can be null in the following conditions:
+     * <ul>
+     *    <li>the editor doesn't support
+     *      {@link InputConnection#requestTextBoundsInfo(RectF, Executor, Consumer)}.</li>
+     *    <li>the editor doesn't have the text bounds information at the moment. (e.g. the editor
+     *    view is not laid out yet.) </li>
+     *    <li> the {@link InputConnection} is or become inactive during the request. </li>
+     * <ul/>
+     */
+    @Nullable
+    public TextBoundsInfo getTextBoundsInfo() {
+        return  mTextBoundsInfo;
+    }
+}
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index 8f590f8..56524a2 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -125,6 +125,7 @@
 import android.view.inputmethod.ExtractedTextRequest;
 import android.view.inputmethod.InputConnection;
 import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.TextAppearanceInfo;
 import android.view.textclassifier.TextClassification;
 import android.view.textclassifier.TextClassificationManager;
 import android.widget.AdapterView.OnItemClickListener;
@@ -4630,14 +4631,17 @@
                     (filter & InputConnection.CURSOR_UPDATE_FILTER_INSERTION_MARKER) != 0;
             boolean includeVisibleLineBounds =
                     (filter & InputConnection.CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS) != 0;
+            boolean includeTextAppearance =
+                    (filter & InputConnection.CURSOR_UPDATE_FILTER_TEXT_APPEARANCE) != 0;
             boolean includeAll =
                     (!includeEditorBounds && !includeCharacterBounds && !includeInsertionMarker
-                    && !includeVisibleLineBounds);
+                    && !includeVisibleLineBounds && !includeTextAppearance);
 
             includeEditorBounds |= includeAll;
             includeCharacterBounds |= includeAll;
             includeInsertionMarker |= includeAll;
             includeVisibleLineBounds |= includeAll;
+            includeTextAppearance |= includeAll;
 
             final CursorAnchorInfo.Builder builder = mSelectionInfoBuilder;
             builder.reset();
@@ -4757,6 +4761,10 @@
                 }
             }
 
+            if (includeTextAppearance) {
+                TextAppearanceInfo textAppearanceInfo = new TextAppearanceInfo(mTextView);
+                builder.setTextAppearanceInfo(textAppearanceInfo);
+            }
             imm.updateCursorAnchorInfo(mTextView, builder.build());
 
             // Drop the immediate flag if any.
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index 5e74381..510a92d 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -116,9 +116,7 @@
  * <p class="note">ListView attempts to reuse view objects in order to improve performance and
  * avoid a lag in response to user scrolls.  To take advantage of this feature, check if the
  * {@code convertView} provided to {@code getView(...)} is null before creating or inflating a new
- * view object.  See
- * <a href="{@docRoot}training/improving-layouts/smooth-scrolling.html">
- * Making ListView Scrolling Smooth</a> for more ways to ensure a smooth user experience.</p>
+ * view object.</p>
  *
  * <p>To specify an action when a user clicks or taps on a single list item, see
  * <a href="{@docRoot}guide/topics/ui/declaring-layout.html#HandlingUserSelections">
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index b5c58fb..1144b59 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -69,6 +69,7 @@
 import android.graphics.BlendMode;
 import android.graphics.Canvas;
 import android.graphics.Insets;
+import android.graphics.Matrix;
 import android.graphics.Paint;
 import android.graphics.Paint.FontMetricsInt;
 import android.graphics.Path;
@@ -200,6 +201,7 @@
 import android.view.inputmethod.RemoveSpaceGesture;
 import android.view.inputmethod.SelectGesture;
 import android.view.inputmethod.SelectRangeGesture;
+import android.view.inputmethod.TextBoundsInfo;
 import android.view.inspector.InspectableProperty;
 import android.view.inspector.InspectableProperty.EnumEntry;
 import android.view.inspector.InspectableProperty.FlagEntry;
@@ -12953,18 +12955,15 @@
         getLocalVisibleRect(rect);
         final RectF visibleRect = new RectF(rect);
 
-        final float[] characterBounds = new float[4 * (endIndex - startIndex)];
-        mLayout.fillCharacterBounds(startIndex, endIndex, characterBounds, 0);
+
+        final float[] characterBounds = getCharacterBounds(startIndex, endIndex,
+                viewportToContentHorizontalOffset, viewportToContentVerticalOffset);
         final int limit = endIndex - startIndex;
         for (int offset = 0; offset < limit; ++offset) {
-            final float left =
-                    characterBounds[offset * 4] + viewportToContentHorizontalOffset;
-            final float top =
-                    characterBounds[offset * 4 + 1] + viewportToContentVerticalOffset;
-            final float right =
-                    characterBounds[offset * 4 + 2] + viewportToContentHorizontalOffset;
-            final float bottom =
-                    characterBounds[offset * 4 + 3] + viewportToContentVerticalOffset;
+            final float left = characterBounds[offset * 4];
+            final float top = characterBounds[offset * 4 + 1];
+            final float right = characterBounds[offset * 4 + 2];
+            final float bottom = characterBounds[offset * 4 + 3];
 
             final boolean hasVisibleRegion = visibleRect.intersects(left, top, right, bottom);
             final boolean hasInVisibleRegion = !visibleRect.contains(left, top, right, bottom);
@@ -12985,6 +12984,149 @@
     }
 
     /**
+     * Return the bounds of the characters in the given range, in TextView's coordinates.
+     *
+     * @param start the start index of the interested text range, inclusive.
+     * @param end the end index of the interested text range, exclusive.
+     * @param layoutLeft the left of the given {@code layout} in the editor view's coordinates.
+     * @param layoutTop  the top of the given {@code layout} in the editor view's coordinates.
+     * @return the character bounds stored in a flattened array, in the editor view's coordinates.
+     */
+    private float[] getCharacterBounds(int start, int end, float layoutLeft, float layoutTop) {
+        final float[] characterBounds = new float[4 * (end - start)];
+        mLayout.fillCharacterBounds(start, end, characterBounds, 0);
+        for (int offset = 0; offset < end - start; ++offset) {
+            characterBounds[4 * offset] += layoutLeft;
+            characterBounds[4 * offset + 1] += layoutTop;
+            characterBounds[4 * offset + 2] += layoutLeft;
+            characterBounds[4 * offset + 3] += layoutTop;
+        }
+        return characterBounds;
+    }
+
+    /**
+     * Creates the {@link TextBoundsInfo} for the text lines that intersects with the {@code rectF}.
+     * @hide
+     */
+    public TextBoundsInfo getTextBoundsInfo(@NonNull RectF rectF) {
+        final Layout layout = getLayout();
+        if (layout == null) {
+            // No valid text layout, return null.
+            return null;
+        }
+        final CharSequence text = layout.getText();
+        if (text == null) {
+            // It's impossible that a layout has no text. Check here to avoid NPE.
+            return null;
+        }
+
+        final Matrix localToGlobalMatrix = new Matrix();
+        transformMatrixToGlobal(localToGlobalMatrix);
+        final Matrix globalToLocalMatrix = new Matrix();
+        if (!localToGlobalMatrix.invert(globalToLocalMatrix)) {
+            // Can't map global rectF to local coordinates, this is almost impossible in practice.
+            return null;
+        }
+
+        final float layoutLeft = viewportToContentHorizontalOffset();
+        final float layoutTop = viewportToContentVerticalOffset();
+
+        final RectF localRectF = new RectF(rectF);
+        globalToLocalMatrix.mapRect(localRectF);
+        localRectF.offset(-layoutLeft, -layoutTop);
+
+        // Text length is 0. There is no character bounds, return empty TextBoundsInfo.
+        // rectF doesn't intersect with the layout, return empty TextBoundsInfo.
+        if (!localRectF.intersects(0f, 0f, layout.getWidth(), layout.getHeight())
+                || text.length() == 0) {
+            final TextBoundsInfo.Builder builder = new TextBoundsInfo.Builder();
+            final SegmentFinder emptySegmentFinder =
+                    new SegmentFinder.DefaultSegmentFinder(new int[0]);
+            builder.setStartAndEnd(0, 0)
+                    .setMatrix(localToGlobalMatrix)
+                    .setCharacterBounds(new float[0])
+                    .setCharacterBidiLevel(new int[0])
+                    .setCharacterFlags(new int[0])
+                    .setGraphemeSegmentFinder(emptySegmentFinder)
+                    .setLineSegmentFinder(emptySegmentFinder)
+                    .setWordSegmentFinder(emptySegmentFinder);
+            return  builder.build();
+        }
+
+        final int startLine = layout.getLineForVertical((int) Math.floor(localRectF.top));
+        final int endLine = layout.getLineForVertical((int) Math.floor(localRectF.bottom));
+        final int start = layout.getLineStart(startLine);
+        final int end = layout.getLineEnd(endLine);
+
+        // Compute character bounds.
+        final float[] characterBounds = getCharacterBounds(start, end, layoutLeft, layoutTop);
+
+        // Compute character flags and BiDi levels.
+        final int[] characterFlags = new int[end - start];
+        final int[] characterBidiLevels = new int[end - start];
+        for (int line = startLine; line <= endLine; ++line) {
+            final int lineStart = layout.getLineStart(line);
+            final int lineEnd = layout.getLineEnd(line);
+            final Layout.Directions directions = layout.getLineDirections(line);
+            for (int i = 0; i < directions.getRunCount(); ++i) {
+                final int runStart = directions.getRunStart(i) + lineStart;
+                final int runEnd = Math.min(runStart + directions.getRunLength(i), lineEnd);
+                final int runLevel = directions.getRunLevel(i);
+                Arrays.fill(characterBidiLevels, runStart - start, runEnd - start, runLevel);
+            }
+
+            final boolean lineIsRtl =
+                    layout.getParagraphDirection(line) == Layout.DIR_RIGHT_TO_LEFT;
+            for (int index = lineStart; index < lineEnd; ++index) {
+                int flags = 0;
+                if (TextUtils.isWhitespace(text.charAt(index))) {
+                    flags |= TextBoundsInfo.FLAG_CHARACTER_WHITESPACE;
+                }
+                if (TextUtils.isPunctuation(Character.codePointAt(text, index))) {
+                    flags |= TextBoundsInfo.FLAG_CHARACTER_PUNCTUATION;
+                }
+                if (TextUtils.isNewline(Character.codePointAt(text, index))) {
+                    flags |= TextBoundsInfo.FLAG_CHARACTER_LINEFEED;
+                }
+                if (lineIsRtl) {
+                    flags |= TextBoundsInfo.FLAG_LINE_IS_RTL;
+                }
+                characterFlags[index - start] = flags;
+            }
+        }
+
+        // Create grapheme SegmentFinder.
+        final SegmentFinder graphemeSegmentFinder =
+                new GraphemeClusterSegmentFinder(text, layout.getPaint());
+
+        // Create word SegmentFinder.
+        final WordIterator wordIterator = getWordIterator();
+        wordIterator.setCharSequence(text, 0, text.length());
+        final SegmentFinder wordSegmentFinder = new WordSegmentFinder(text, wordIterator);
+
+        // Create line SegmentFinder.
+        final int lineCount = endLine - startLine + 1;
+        final int[] lineRanges = new int[2 * lineCount];
+        for (int line = startLine; line <= endLine; ++line) {
+            final int offset = line - startLine;
+            lineRanges[2 * offset] = layout.getLineStart(line);
+            lineRanges[2 * offset + 1] = layout.getLineEnd(line);
+        }
+        final SegmentFinder lineSegmentFinder = new SegmentFinder.DefaultSegmentFinder(lineRanges);
+
+        final TextBoundsInfo.Builder builder = new TextBoundsInfo.Builder();
+        builder.setStartAndEnd(start, end)
+                .setMatrix(localToGlobalMatrix)
+                .setCharacterBounds(characterBounds)
+                .setCharacterBidiLevel(characterBidiLevels)
+                .setCharacterFlags(characterFlags)
+                .setGraphemeSegmentFinder(graphemeSegmentFinder)
+                .setLineSegmentFinder(lineSegmentFinder)
+                .setWordSegmentFinder(wordSegmentFinder);
+        return  builder.build();
+    }
+
+    /**
      * @hide
      */
     public boolean isPositionVisible(final float positionX, final float positionY) {
diff --git a/core/java/android/window/IWindowOrganizerController.aidl b/core/java/android/window/IWindowOrganizerController.aidl
index 36eaf49..57e0ce8 100644
--- a/core/java/android/window/IWindowOrganizerController.aidl
+++ b/core/java/android/window/IWindowOrganizerController.aidl
@@ -105,4 +105,7 @@
 
     /** @return An interface enabling the transition players to report its metrics. */
     ITransitionMetricsReporter getTransitionMetricsReporter();
+
+    /** @return The transaction queue token used by WM. */
+    IBinder getApplyToken();
 }
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 8815ab3..0956a71 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -141,6 +141,10 @@
     /** The first unused bit. This can be used by remotes to attach custom flags to this change. */
     public static final int FLAG_FIRST_CUSTOM = 1 << 17;
 
+    /** The change belongs to a window that won't contain activities. */
+    public static final int FLAGS_IS_NON_APP_WINDOW =
+            FLAG_IS_WALLPAPER | FLAG_IS_INPUT_METHOD | FLAG_IS_SYSTEM_WINDOW;
+
     /** @hide */
     @IntDef(prefix = { "FLAG_" }, value = {
             FLAG_NONE,
@@ -579,11 +583,16 @@
             return mFlags;
         }
 
-        /** Whether the given change flags has included in this change. */
+        /** Whether this change contains any of the given change flags. */
         public boolean hasFlags(@ChangeFlags int flags) {
             return (mFlags & flags) != 0;
         }
 
+        /** Whether this change contains all of the given change flags. */
+        public boolean hasAllFlags(@ChangeFlags int flags) {
+            return (mFlags & flags) == flags;
+        }
+
         /**
          * @return the bounds of the container before the change. It may be empty if the container
          * is coming into existence.
diff --git a/core/java/android/window/WindowOrganizer.java b/core/java/android/window/WindowOrganizer.java
index 2a80d02..930aaa2 100644
--- a/core/java/android/window/WindowOrganizer.java
+++ b/core/java/android/window/WindowOrganizer.java
@@ -26,6 +26,7 @@
 import android.os.RemoteException;
 import android.util.Singleton;
 import android.view.RemoteAnimationAdapter;
+import android.view.SurfaceControl;
 
 /**
  * Base class for organizing specific types of windows like Tasks and DisplayAreas
@@ -184,6 +185,26 @@
         }
     }
 
+    /**
+     * Use WM's transaction-queue instead of Shell's independent one. This is necessary
+     * if WM and Shell need to coordinate transactions (eg. for shell transitions).
+     * @return true if successful, false otherwise.
+     * @hide
+     */
+    public boolean shareTransactionQueue() {
+        final IBinder wmApplyToken;
+        try {
+            wmApplyToken = getWindowOrganizerController().getApplyToken();
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+        if (wmApplyToken == null) {
+            return false;
+        }
+        SurfaceControl.Transaction.setDefaultApplyToken(wmApplyToken);
+        return true;
+    }
+
     static IWindowOrganizerController getWindowOrganizerController() {
         return IWindowOrganizerControllerSingleton.get();
     }
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index 44997b4..0f64f6d 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -556,6 +556,11 @@
             "show_stop_button_for_user_allowlisted_apps";
 
     /**
+     * (boolean) Whether to show notification volume control slider separate from ring.
+     */
+    public static final String VOLUME_SEPARATE_NOTIFICATION = "volume_separate_notification";
+
+    /**
      * (boolean) Whether the clipboard overlay is enabled.
      */
     public static final String CLIPBOARD_OVERLAY_ENABLED = "clipboard_overlay_enabled";
diff --git a/core/java/com/android/internal/expresslog/Counter.java b/core/java/com/android/internal/expresslog/Counter.java
new file mode 100644
index 0000000..7571073
--- /dev/null
+++ b/core/java/com/android/internal/expresslog/Counter.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.expresslog;
+
+import android.annotation.NonNull;
+
+import com.android.internal.util.FrameworkStatsLog;
+
+/** Counter encapsulates StatsD write API calls */
+public final class Counter {
+
+    // Not instantiable.
+    private Counter() {}
+
+    /**
+     * Increments Telemetry Express Counter metric by 1
+     * @hide
+     */
+    public static void logIncrement(@NonNull String metricId) {
+        logIncrement(metricId, 1);
+    }
+
+    /**
+     * Increments Telemetry Express Counter metric by arbitrary value
+     * @hide
+     */
+    public static void logIncrement(@NonNull String metricId, long amount) {
+        final long metricIdHash = hashString(metricId);
+        FrameworkStatsLog.write(FrameworkStatsLog.EXPRESS_EVENT_REPORTED, metricIdHash, amount);
+    }
+
+    private static native long hashString(String stringToHash);
+}
diff --git a/core/java/com/android/internal/inputmethod/EditableInputConnection.java b/core/java/com/android/internal/inputmethod/EditableInputConnection.java
index f600c36..330ff8e 100644
--- a/core/java/com/android/internal/inputmethod/EditableInputConnection.java
+++ b/core/java/com/android/internal/inputmethod/EditableInputConnection.java
@@ -25,6 +25,7 @@
 import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.graphics.RectF;
 import android.os.Bundle;
 import android.text.Editable;
 import android.text.Selection;
@@ -46,9 +47,12 @@
 import android.view.inputmethod.RemoveSpaceGesture;
 import android.view.inputmethod.SelectGesture;
 import android.view.inputmethod.SelectRangeGesture;
+import android.view.inputmethod.TextBoundsInfo;
+import android.view.inputmethod.TextBoundsInfoResult;
 import android.widget.TextView;
 
 import java.util.concurrent.Executor;
+import java.util.function.Consumer;
 import java.util.function.IntConsumer;
 
 /**
@@ -233,7 +237,9 @@
                 | InputConnection.CURSOR_UPDATE_MONITOR;
         final int knownFilterFlags = InputConnection.CURSOR_UPDATE_FILTER_EDITOR_BOUNDS
                 | InputConnection.CURSOR_UPDATE_FILTER_INSERTION_MARKER
-                | InputConnection.CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS;
+                | InputConnection.CURSOR_UPDATE_FILTER_CHARACTER_BOUNDS
+                | InputConnection.CURSOR_UPDATE_FILTER_VISIBLE_LINE_BOUNDS
+                | InputConnection.CURSOR_UPDATE_FILTER_TEXT_APPEARANCE;
 
         // It is possible that any other bit is used as a valid flag in a future release.
         // We should reject the entire request in such a case.
@@ -262,6 +268,23 @@
     }
 
     @Override
+    public void requestTextBoundsInfo(
+            @NonNull RectF rectF, @Nullable @CallbackExecutor Executor executor,
+            @NonNull Consumer<TextBoundsInfoResult> consumer) {
+        final TextBoundsInfo textBoundsInfo = mTextView.getTextBoundsInfo(rectF);
+        final int resultCode;
+        if (textBoundsInfo != null) {
+            resultCode = TextBoundsInfoResult.CODE_SUCCESS;
+        } else {
+            resultCode = TextBoundsInfoResult.CODE_FAILED;
+        }
+        final TextBoundsInfoResult textBoundsInfoResult =
+                new TextBoundsInfoResult(resultCode, textBoundsInfo);
+
+        executor.execute(() -> consumer.accept(textBoundsInfoResult));
+    }
+
+    @Override
     public boolean setImeConsumesInput(boolean imeConsumesInput) {
         if (mTextView == null) {
             return super.setImeConsumesInput(imeConsumesInput);
diff --git a/core/java/com/android/internal/inputmethod/IInputMethodManagerGlobal.java b/core/java/com/android/internal/inputmethod/IInputMethodManagerGlobal.java
deleted file mode 100644
index 5392bdc..0000000
--- a/core/java/com/android/internal/inputmethod/IInputMethodManagerGlobal.java
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.internal.inputmethod;
-
-import android.annotation.AnyThread;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.RequiresNoPermission;
-import android.annotation.RequiresPermission;
-import android.content.Context;
-import android.os.RemoteException;
-import android.os.ServiceManager;
-
-import com.android.internal.view.IInputMethodManager;
-
-import java.util.function.Consumer;
-
-/**
- * A global wrapper to directly invoke {@link IInputMethodManager} IPCs.
- *
- * <p>All public static methods are guaranteed to be thread-safe.</p>
- *
- * <p>All public methods are guaranteed to do nothing when {@link IInputMethodManager} is
- * unavailable.</p>
- */
-public final class IInputMethodManagerGlobal {
-    @Nullable
-    private static volatile IInputMethodManager sServiceCache = null;
-
-    /**
-     * @return {@code true} if {@link IInputMethodManager} is available.
-     */
-    @AnyThread
-    public static boolean isAvailable() {
-        return getService() != null;
-    }
-
-    @AnyThread
-    @Nullable
-    private static IInputMethodManager getService() {
-        IInputMethodManager service = sServiceCache;
-        if (service == null) {
-            service = IInputMethodManager.Stub.asInterface(
-                    ServiceManager.getService(Context.INPUT_METHOD_SERVICE));
-            if (service == null) {
-                return null;
-            }
-            sServiceCache = service;
-        }
-        return service;
-    }
-
-    @AnyThread
-    private static void handleRemoteExceptionOrRethrow(@NonNull RemoteException e,
-            @Nullable Consumer<RemoteException> exceptionHandler) {
-        if (exceptionHandler != null) {
-            exceptionHandler.accept(e);
-        } else {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Invokes {@link IInputMethodManager#startProtoDump(byte[], int, String)}.
-     *
-     * @param protoDump client or service side information to be stored by the server
-     * @param source where the information is coming from, refer to
-     *               {@link ImeTracing#IME_TRACING_FROM_CLIENT} and
-     *               {@link ImeTracing#IME_TRACING_FROM_IMS}
-     * @param where where the information is coming from.
-     * @param exceptionHandler an optional {@link RemoteException} handler.
-     */
-    @RequiresNoPermission
-    @AnyThread
-    public static void startProtoDump(byte[] protoDump, int source, String where,
-            @Nullable Consumer<RemoteException> exceptionHandler) {
-        final IInputMethodManager service = getService();
-        if (service == null) {
-            return;
-        }
-        try {
-            service.startProtoDump(protoDump, source, where);
-        } catch (RemoteException e) {
-            handleRemoteExceptionOrRethrow(e, exceptionHandler);
-        }
-    }
-
-    /**
-     * Invokes {@link IInputMethodManager#startImeTrace()}.
-     *
-     * @param exceptionHandler an optional {@link RemoteException} handler.
-     */
-    @RequiresPermission(android.Manifest.permission.CONTROL_UI_TRACING)
-    @AnyThread
-    public static void startImeTrace(@Nullable Consumer<RemoteException> exceptionHandler) {
-        final IInputMethodManager service = getService();
-        if (service == null) {
-            return;
-        }
-        try {
-            service.startImeTrace();
-        } catch (RemoteException e) {
-            handleRemoteExceptionOrRethrow(e, exceptionHandler);
-        }
-    }
-
-    /**
-     * Invokes {@link IInputMethodManager#stopImeTrace()}.
-     *
-     * @param exceptionHandler an optional {@link RemoteException} handler.
-     */
-    @RequiresPermission(android.Manifest.permission.CONTROL_UI_TRACING)
-    @AnyThread
-    public static void stopImeTrace(@Nullable Consumer<RemoteException> exceptionHandler) {
-        final IInputMethodManager service = getService();
-        if (service == null) {
-            return;
-        }
-        try {
-            service.stopImeTrace();
-        } catch (RemoteException e) {
-            handleRemoteExceptionOrRethrow(e, exceptionHandler);
-        }
-    }
-
-    /**
-     * Invokes {@link IInputMethodManager#isImeTraceEnabled()}.
-     *
-     * @return The return value of {@link IInputMethodManager#isImeTraceEnabled()}.
-     */
-    @RequiresNoPermission
-    @AnyThread
-    public static boolean isImeTraceEnabled() {
-        final IInputMethodManager service = getService();
-        if (service == null) {
-            return false;
-        }
-        try {
-            return service.isImeTraceEnabled();
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Invokes {@link IInputMethodManager#removeImeSurface()}
-     */
-    @RequiresPermission(android.Manifest.permission.INTERNAL_SYSTEM_WINDOW)
-    @AnyThread
-    public static void removeImeSurface(@Nullable Consumer<RemoteException> exceptionHandler) {
-        final IInputMethodManager service = getService();
-        if (service == null) {
-            return;
-        }
-        try {
-            service.removeImeSurface();
-        } catch (RemoteException e) {
-            handleRemoteExceptionOrRethrow(e, exceptionHandler);
-        }
-    }
-}
diff --git a/core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl b/core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl
index ea5c9a3..81e060d 100644
--- a/core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl
+++ b/core/java/com/android/internal/inputmethod/IRemoteInputConnection.aidl
@@ -16,20 +16,15 @@
 
 package com.android.internal.inputmethod;
 
+import android.graphics.RectF;
 import android.os.Bundle;
 import android.os.ResultReceiver;
 import android.view.KeyEvent;
 import android.view.inputmethod.CompletionInfo;
 import android.view.inputmethod.CorrectionInfo;
-import android.view.inputmethod.DeleteGesture;
-import android.view.inputmethod.DeleteRangeGesture;
 import android.view.inputmethod.ExtractedTextRequest;
 import android.view.inputmethod.InputContentInfo;
-import android.view.inputmethod.InsertGesture;
-import android.view.inputmethod.JoinOrSplitGesture;
-import android.view.inputmethod.RemoveSpaceGesture;
-import android.view.inputmethod.SelectGesture;
-import android.view.inputmethod.SelectRangeGesture;
+import android.view.inputmethod.ParcelableHandwritingGesture;
 import android.view.inputmethod.TextAttribute;
 
 import com.android.internal.infra.AndroidFuture;
@@ -94,26 +89,8 @@
     void performPrivateCommand(in InputConnectionCommandHeader header, String action,
             in Bundle data);
 
-    void performHandwritingSelectGesture(in InputConnectionCommandHeader header,
-            in SelectGesture gesture, in ResultReceiver resultReceiver);
-
-    void performHandwritingSelectRangeGesture(in InputConnectionCommandHeader header,
-            in SelectRangeGesture gesture, in ResultReceiver resultReceiver);
-
-    void performHandwritingInsertGesture(in InputConnectionCommandHeader header,
-            in InsertGesture gesture, in ResultReceiver resultReceiver);
-
-    void performHandwritingDeleteGesture(in InputConnectionCommandHeader header,
-            in DeleteGesture gesture, in ResultReceiver resultReceiver);
-
-    void performHandwritingDeleteRangeGesture(in InputConnectionCommandHeader header,
-                in DeleteRangeGesture gesture, in ResultReceiver resultReceiver);
-
-    void performHandwritingRemoveSpaceGesture(in InputConnectionCommandHeader header,
-            in RemoveSpaceGesture gesture, in ResultReceiver resultReceiver);
-
-    void performHandwritingJoinOrSplitGesture(in InputConnectionCommandHeader header,
-            in JoinOrSplitGesture gesture, in ResultReceiver resultReceiver);
+    void performHandwritingGesture(in InputConnectionCommandHeader header,
+            in ParcelableHandwritingGesture gesture, in ResultReceiver resultReceiver);
 
     void setComposingRegion(in InputConnectionCommandHeader header, int start, int end);
 
@@ -130,6 +107,9 @@
                 int cursorUpdateMode, int cursorUpdateFilter, int imeDisplayId,
                  in AndroidFuture future /* T=Boolean */);
 
+    void requestTextBoundsInfo(in InputConnectionCommandHeader header, in RectF rect,
+           in ResultReceiver resultReceiver /* T=TextBoundsInfoResult */);
+
     void commitContent(in InputConnectionCommandHeader header, in InputContentInfo inputContentInfo,
             int flags, in Bundle opts, in AndroidFuture future /* T=Boolean */);
 
diff --git a/core/java/com/android/internal/inputmethod/ImeTracing.java b/core/java/com/android/internal/inputmethod/ImeTracing.java
index a4328cc..e6a9b54 100644
--- a/core/java/com/android/internal/inputmethod/ImeTracing.java
+++ b/core/java/com/android/internal/inputmethod/ImeTracing.java
@@ -22,6 +22,7 @@
 import android.util.Log;
 import android.util.proto.ProtoOutputStream;
 import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputMethodManagerGlobal;
 
 import java.io.PrintWriter;
 
@@ -44,7 +45,7 @@
     private static ImeTracing sInstance;
     static boolean sEnabled = false;
 
-    private final boolean mIsAvailable = IInputMethodManagerGlobal.isAvailable();
+    private final boolean mIsAvailable = InputMethodManagerGlobal.isImeTraceAvailable();
 
     protected boolean mDumpInProgress;
     protected final Object mDumpInProgressLock = new Object();
@@ -81,7 +82,7 @@
      * @param where
      */
     public void sendToService(byte[] protoDump, int source, String where) {
-        IInputMethodManagerGlobal.startProtoDump(protoDump, source, where,
+        InputMethodManagerGlobal.startProtoDump(protoDump, source, where,
                 e -> Log.e(TAG, "Exception while sending ime-related dump to server", e));
     }
 
@@ -90,7 +91,7 @@
      */
     @RequiresPermission(android.Manifest.permission.CONTROL_UI_TRACING)
     public final void startImeTrace() {
-        IInputMethodManagerGlobal.startImeTrace(e -> Log.e(TAG, "Could not start ime trace.", e));
+        InputMethodManagerGlobal.startImeTrace(e -> Log.e(TAG, "Could not start ime trace.", e));
     }
 
     /**
@@ -98,7 +99,7 @@
      */
     @RequiresPermission(android.Manifest.permission.CONTROL_UI_TRACING)
     public final void stopImeTrace() {
-        IInputMethodManagerGlobal.stopImeTrace(e -> Log.e(TAG, "Could not stop ime trace.", e));
+        InputMethodManagerGlobal.stopImeTrace(e -> Log.e(TAG, "Could not stop ime trace.", e));
     }
 
     /**
diff --git a/core/java/com/android/internal/inputmethod/ImeTracingClientImpl.java b/core/java/com/android/internal/inputmethod/ImeTracingClientImpl.java
index 4caca84..95ed4ed 100644
--- a/core/java/com/android/internal/inputmethod/ImeTracingClientImpl.java
+++ b/core/java/com/android/internal/inputmethod/ImeTracingClientImpl.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.util.proto.ProtoOutputStream;
 import android.view.inputmethod.InputMethodManager;
+import android.view.inputmethod.InputMethodManagerGlobal;
 
 import java.io.PrintWriter;
 
@@ -28,7 +29,7 @@
  */
 class ImeTracingClientImpl extends ImeTracing {
     ImeTracingClientImpl() {
-        sEnabled = IInputMethodManagerGlobal.isImeTraceEnabled();
+        sEnabled = InputMethodManagerGlobal.isImeTraceEnabled();
     }
 
     @Override
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl b/core/java/com/android/internal/inputmethod/InputMethodSubtypeHandle.aidl
similarity index 80%
rename from packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
rename to core/java/com/android/internal/inputmethod/InputMethodSubtypeHandle.aidl
index 1550ab3..18bd6e5 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
+++ b/core/java/com/android/internal/inputmethod/InputMethodSubtypeHandle.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.systemui.shared.system;
+package com.android.internal.inputmethod;
 
-parcelable RemoteTransitionCompat;
+parcelable InputMethodSubtypeHandle;
diff --git a/core/java/com/android/internal/inputmethod/InputMethodSubtypeHandle.java b/core/java/com/android/internal/inputmethod/InputMethodSubtypeHandle.java
new file mode 100644
index 0000000..780c637
--- /dev/null
+++ b/core/java/com/android/internal/inputmethod/InputMethodSubtypeHandle.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.inputmethod;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.AnyThread;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils.SimpleStringSplitter;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+import java.security.InvalidParameterException;
+import java.util.Objects;
+
+/**
+ * A stable and serializable identifier for the pair of {@link InputMethodInfo#getId()} and
+ * {@link android.view.inputmethod.InputMethodSubtype}.
+ *
+ * <p>To save {@link InputMethodSubtypeHandle} to storage, call {@link #toStringHandle()} to get a
+ * {@link String} handle and just save it.  Once you load a {@link String} handle, you can obtain a
+ * {@link InputMethodSubtypeHandle} instance from {@link #of(String)}.</p>
+ *
+ * <p>For better readability, consider specifying {@link RawHandle} annotation to {@link String}
+ * object when it is a raw {@link String} handle.</p>
+ */
+public final class InputMethodSubtypeHandle implements Parcelable {
+    private static final String SUBTYPE_TAG = "subtype";
+    private static final char DATA_SEPARATOR = ':';
+
+    /**
+     * Can be used to annotate {@link String} object if it is raw handle format.
+     */
+    @Retention(SOURCE)
+    @Target({ElementType.METHOD, ElementType.FIELD, ElementType.LOCAL_VARIABLE,
+            ElementType.PARAMETER})
+    public @interface RawHandle {
+    }
+
+    /**
+     * The main content of this {@link InputMethodSubtypeHandle}.  Is designed to be safe to be
+     * saved into storage.
+     */
+    @RawHandle
+    private final String mHandle;
+
+    /**
+     * Encode {@link InputMethodInfo} and {@link InputMethodSubtype#hashCode()} into
+     * {@link RawHandle}.
+     *
+     * @param imeId {@link InputMethodInfo#getId()} to be used.
+     * @param subtypeHashCode {@link InputMethodSubtype#hashCode()} to be used.
+     * @return The encoded {@link RawHandle} string.
+     */
+    @AnyThread
+    @RawHandle
+    @NonNull
+    private static String encodeHandle(@NonNull String imeId, int subtypeHashCode) {
+        return imeId + DATA_SEPARATOR + SUBTYPE_TAG + DATA_SEPARATOR + subtypeHashCode;
+    }
+
+    private InputMethodSubtypeHandle(@NonNull String handle) {
+        mHandle = handle;
+    }
+
+    /**
+     * Creates {@link InputMethodSubtypeHandle} from {@link InputMethodInfo} and
+     * {@link InputMethodSubtype}.
+     *
+     * @param imi {@link InputMethodInfo} to be used.
+     * @param subtype {@link InputMethodSubtype} to be used.
+     * @return A {@link InputMethodSubtypeHandle} object.
+     */
+    @AnyThread
+    @NonNull
+    public static InputMethodSubtypeHandle of(
+            @NonNull InputMethodInfo imi, @Nullable InputMethodSubtype subtype) {
+        final int subtypeHashCode =
+                subtype != null ? subtype.hashCode() : InputMethodSubtype.SUBTYPE_ID_NONE;
+        return new InputMethodSubtypeHandle(encodeHandle(imi.getId(), subtypeHashCode));
+    }
+
+    /**
+     * Creates {@link InputMethodSubtypeHandle} from a {@link RawHandle} {@link String}, which can
+     * be obtained by {@link #toStringHandle()}.
+     *
+     * @param stringHandle {@link RawHandle} {@link String} to be parsed.
+     * @return A {@link InputMethodSubtypeHandle} object.
+     * @throws NullPointerException when {@code stringHandle} is {@code null}
+     * @throws InvalidParameterException when {@code stringHandle} is not a valid {@link RawHandle}.
+     */
+    @AnyThread
+    @NonNull
+    public static InputMethodSubtypeHandle of(@RawHandle @NonNull String stringHandle) {
+        final SimpleStringSplitter splitter = new SimpleStringSplitter(DATA_SEPARATOR);
+        splitter.setString(Objects.requireNonNull(stringHandle));
+        if (!splitter.hasNext()) {
+            throw new InvalidParameterException("Invalid handle=" + stringHandle);
+        }
+        final String imeId = splitter.next();
+        final ComponentName componentName = ComponentName.unflattenFromString(imeId);
+        if (componentName == null) {
+            throw new InvalidParameterException("Invalid handle=" + stringHandle);
+        }
+        // TODO: Consolidate IME ID validation logic into one place.
+        if (!Objects.equals(componentName.flattenToShortString(), imeId)) {
+            throw new InvalidParameterException("Invalid handle=" + stringHandle);
+        }
+        if (!splitter.hasNext()) {
+            throw new InvalidParameterException("Invalid handle=" + stringHandle);
+        }
+        final String source = splitter.next();
+        if (!Objects.equals(source, SUBTYPE_TAG)) {
+            throw new InvalidParameterException("Invalid handle=" + stringHandle);
+        }
+        if (!splitter.hasNext()) {
+            throw new InvalidParameterException("Invalid handle=" + stringHandle);
+        }
+        final String hashCodeStr = splitter.next();
+        if (splitter.hasNext()) {
+            throw new InvalidParameterException("Invalid handle=" + stringHandle);
+        }
+        final int subtypeHashCode;
+        try {
+            subtypeHashCode = Integer.parseInt(hashCodeStr);
+        } catch (NumberFormatException ignore) {
+            throw new InvalidParameterException("Invalid handle=" + stringHandle);
+        }
+
+        // Redundant expressions (e.g. "0001" instead of "1") are not allowed.
+        if (!Objects.equals(encodeHandle(imeId, subtypeHashCode), stringHandle)) {
+            throw new InvalidParameterException("Invalid handle=" + stringHandle);
+        }
+
+        return new InputMethodSubtypeHandle(stringHandle);
+    }
+
+    /**
+     * @return {@link ComponentName} of the input method.
+     * @see InputMethodInfo#getComponent()
+     */
+    @AnyThread
+    @NonNull
+    public ComponentName getComponentName() {
+        return ComponentName.unflattenFromString(getImeId());
+    }
+
+    /**
+     * @return IME ID.
+     * @see InputMethodInfo#getId()
+     */
+    @AnyThread
+    @NonNull
+    public String getImeId() {
+        return mHandle.substring(0, mHandle.indexOf(DATA_SEPARATOR));
+    }
+
+    /**
+     * @return {@link RawHandle} {@link String} data that should be stable and persistable.
+     * @see #of(String)
+     */
+    @RawHandle
+    @AnyThread
+    @NonNull
+    public String toStringHandle() {
+        return mHandle;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @AnyThread
+    @Override
+    public boolean equals(Object obj) {
+        if (!(obj instanceof InputMethodSubtypeHandle)) {
+            return false;
+        }
+        final InputMethodSubtypeHandle that = (InputMethodSubtypeHandle) obj;
+        return Objects.equals(mHandle, that.mHandle);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @AnyThread
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(mHandle);
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @AnyThread
+    @NonNull
+    @Override
+    public String toString() {
+        return "InputMethodSubtypeHandle{mHandle=" + mHandle + "}";
+    }
+
+    /**
+     * {@link Creator} for parcelable.
+     */
+    public static final Creator<InputMethodSubtypeHandle> CREATOR = new Creator<>() {
+        @Override
+        public InputMethodSubtypeHandle createFromParcel(Parcel in) {
+            return of(in.readString8());
+        }
+
+        @Override
+        public InputMethodSubtypeHandle[] newArray(int size) {
+            return new InputMethodSubtypeHandle[size];
+        }
+    };
+
+    /**
+     * {@inheritDoc}
+     */
+    @AnyThread
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @AnyThread
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString8(toStringHandle());
+    }
+}
diff --git a/core/java/com/android/internal/os/RoSystemProperties.java b/core/java/com/android/internal/os/RoSystemProperties.java
index 98d81c9..6870d09 100644
--- a/core/java/com/android/internal/os/RoSystemProperties.java
+++ b/core/java/com/android/internal/os/RoSystemProperties.java
@@ -51,6 +51,15 @@
     // ------ ro.fw.* ------------ //
     public static final boolean FW_SYSTEM_USER_SPLIT =
             SystemProperties.getBoolean("ro.fw.system_user_split", false);
+    /**
+     * Indicates whether the device should run in headless system user mode,
+     *   in which user 0 only runs the system, not a real user.
+     * <p>WARNING about changing this value during an non-wiping update (OTA):
+     *   <li>If this value is modified via an update, the change will have no effect, since an
+     *       already-existing system user cannot change its mode.
+     *   <li>Changing this value during an OTA from a pre-R device is not permitted; attempting to
+     *       do so will corrupt the system user.
+     */
     public static final boolean MULTIUSER_HEADLESS_SYSTEM_USER =
             SystemProperties.getBoolean("ro.fw.mu.headless_system_user", false);
 
diff --git a/core/java/com/android/internal/security/TEST_MAPPING b/core/java/com/android/internal/security/TEST_MAPPING
index 9a5e90e..803760c 100644
--- a/core/java/com/android/internal/security/TEST_MAPPING
+++ b/core/java/com/android/internal/security/TEST_MAPPING
@@ -1,6 +1,17 @@
 {
   "presubmit": [
     {
+      "name": "FrameworksCoreTests",
+      "options": [
+        {
+          "include-filter": "com.android.internal.security."
+        },
+        {
+          "include-annotation": "android.platform.test.annotations.Presubmit"
+        }
+      ]
+    },
+    {
       "name": "ApkVerityTest",
       "file_patterns": ["VerityUtils\\.java"]
     }
diff --git a/core/java/com/android/internal/security/VerityUtils.java b/core/java/com/android/internal/security/VerityUtils.java
index cb5820f..7f45c09 100644
--- a/core/java/com/android/internal/security/VerityUtils.java
+++ b/core/java/com/android/internal/security/VerityUtils.java
@@ -23,10 +23,28 @@
 import android.system.OsConstants;
 import android.util.Slog;
 
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import com.android.internal.org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import com.android.internal.org.bouncycastle.cms.CMSException;
+import com.android.internal.org.bouncycastle.cms.CMSProcessableByteArray;
+import com.android.internal.org.bouncycastle.cms.CMSSignedData;
+import com.android.internal.org.bouncycastle.cms.SignerInformation;
+import com.android.internal.org.bouncycastle.cms.SignerInformationVerifier;
+import com.android.internal.org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
+import com.android.internal.org.bouncycastle.operator.OperatorCreationException;
+
 import java.io.File;
 import java.io.IOException;
+import java.io.InputStream;
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.charset.StandardCharsets;
 import java.nio.file.Files;
 import java.nio.file.Paths;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
 
 /** Provides fsverity related operations. */
 public abstract class VerityUtils {
@@ -91,6 +109,91 @@
     }
 
     /**
+     * Verifies the signature over the fs-verity digest using the provided certificate.
+     *
+     * This method should only be used by any existing fs-verity use cases that require
+     * PKCS#7 signature verification, if backward compatibility is necessary.
+     *
+     * Since PKCS#7 is too flexible, for the current specific need, only specific configuration
+     * will be accepted:
+     * <ul>
+     *   <li>Must use SHA256 as the digest algorithm
+     *   <li>Must use rsaEncryption as signature algorithm
+     *   <li>Must be detached / without content
+     *   <li>Must not include any signed or unsigned attributes
+     * </ul>
+     *
+     * It is up to the caller to provide an appropriate/trusted certificate.
+     *
+     * @param signatureBlock byte array of a PKCS#7 detached signature
+     * @param digest fs-verity digest with the common configuration using sha256
+     * @param derCertInputStream an input stream of a X.509 certificate in DER
+     * @return whether the verification succeeds
+     */
+    public static boolean verifyPkcs7DetachedSignature(@NonNull byte[] signatureBlock,
+            @NonNull byte[] digest, @NonNull InputStream derCertInputStream) {
+        if (digest.length != 32) {
+            Slog.w(TAG, "Only sha256 is currently supported");
+            return false;
+        }
+
+        try {
+            CMSSignedData signedData = new CMSSignedData(
+                    new CMSProcessableByteArray(toFormattedDigest(digest)),
+                    signatureBlock);
+
+            if (!signedData.isDetachedSignature()) {
+                Slog.w(TAG, "Expect only detached siganture");
+                return false;
+            }
+            if (!signedData.getCertificates().getMatches(null).isEmpty()) {
+                Slog.w(TAG, "Expect no certificate in signature");
+                return false;
+            }
+            if (!signedData.getCRLs().getMatches(null).isEmpty()) {
+                Slog.w(TAG, "Expect no CRL in signature");
+                return false;
+            }
+
+            X509Certificate trustedCert = (X509Certificate) CertificateFactory.getInstance("X.509")
+                    .generateCertificate(derCertInputStream);
+            SignerInformationVerifier verifier = new JcaSimpleSignerInfoVerifierBuilder()
+                    .build(trustedCert);
+
+            // Verify any signature with the trusted certificate.
+            for (SignerInformation si : signedData.getSignerInfos().getSigners()) {
+                // To be the most strict while dealing with the complicated PKCS#7 signature, reject
+                // everything we don't need.
+                if (si.getSignedAttributes() != null && si.getSignedAttributes().size() > 0) {
+                    Slog.w(TAG, "Unexpected signed attributes");
+                    return false;
+                }
+                if (si.getUnsignedAttributes() != null && si.getUnsignedAttributes().size() > 0) {
+                    Slog.w(TAG, "Unexpected unsigned attributes");
+                    return false;
+                }
+                if (!NISTObjectIdentifiers.id_sha256.getId().equals(si.getDigestAlgOID())) {
+                    Slog.w(TAG, "Unsupported digest algorithm OID: " + si.getDigestAlgOID());
+                    return false;
+                }
+                if (!PKCSObjectIdentifiers.rsaEncryption.getId().equals(si.getEncryptionAlgOID())) {
+                    Slog.w(TAG, "Unsupported encryption algorithm OID: "
+                            + si.getEncryptionAlgOID());
+                    return false;
+                }
+
+                if (si.verify(verifier)) {
+                    return true;
+                }
+            }
+            return false;
+        } catch (CertificateException | CMSException | OperatorCreationException e) {
+            Slog.w(TAG, "Error occurred during the PKCS#7 signature verification", e);
+        }
+        return false;
+    }
+
+    /**
      * Returns fs-verity digest for the file if enabled, otherwise returns null. The digest is a
      * hash of root hash of fs-verity's Merkle tree with extra metadata.
      *
@@ -110,6 +213,19 @@
         return result;
     }
 
+    /** @hide */
+    @VisibleForTesting
+    public static byte[] toFormattedDigest(byte[] digest) {
+        // Construct fsverity_formatted_digest used in fs-verity's built-in signature verification.
+        ByteBuffer buffer = ByteBuffer.allocate(12 + digest.length); // struct size + sha256 size
+        buffer.order(ByteOrder.LITTLE_ENDIAN);
+        buffer.put("FSVerity".getBytes(StandardCharsets.US_ASCII));
+        buffer.putShort((short) 1); // FS_VERITY_HASH_ALG_SHA256
+        buffer.putShort((short) digest.length);
+        buffer.put(digest);
+        return buffer.array();
+    }
+
     private static native int enableFsverityNative(@NonNull String filePath,
             @NonNull byte[] pkcs7Signature);
     private static native int measureFsverityNative(@NonNull String filePath,
diff --git a/core/java/com/android/internal/statusbar/IStatusBar.aidl b/core/java/com/android/internal/statusbar/IStatusBar.aidl
index 1d4b246..edbdc86 100644
--- a/core/java/com/android/internal/statusbar/IStatusBar.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBar.aidl
@@ -23,13 +23,12 @@
 import android.hardware.biometrics.IBiometricContextListener;
 import android.hardware.biometrics.IBiometricSysuiReceiver;
 import android.hardware.biometrics.PromptInfo;
-import android.hardware.fingerprint.IUdfpsHbmListener;
+import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
 import android.media.INearbyMediaDevicesProvider;
 import android.media.MediaRoute2Info;
 import android.os.Bundle;
 import android.os.ParcelFileDescriptor;
 import android.service.notification.StatusBarNotification;
-import android.view.InsetsVisibilities;
 
 import com.android.internal.statusbar.IAddTileResultCallback;
 import com.android.internal.statusbar.IUndoMediaTransferCallback;
@@ -175,9 +174,9 @@
     void setBiometicContextListener(in IBiometricContextListener listener);
 
     /**
-     * Sets an instance of IUdfpsHbmListener for UdfpsController.
+     * Sets an instance of IUdfpsRefreshRateRequestCallback for UdfpsController.
      */
-    void setUdfpsHbmListener(in IUdfpsHbmListener listener);
+    void setUdfpsRefreshRateCallback(in IUdfpsRefreshRateRequestCallback callback);
 
     /**
      * Notifies System UI that the display is ready to show system decorations.
@@ -201,13 +200,13 @@
      *                         stacks.
      * @param navbarColorManagedByIme {@code true} if navigation bar color is managed by IME.
      * @param behavior the behavior of the focused window.
-     * @param requestedVisibilities the collection of the requested visibilities of system insets.
+     * @param requestedVisibleTypes the collection of insets types requested visible.
      * @param packageName the package name of the focused app.
      * @param letterboxDetails a set of letterbox details of apps visible on the screen.
      */
     void onSystemBarAttributesChanged(int displayId, int appearance,
             in AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
-            int behavior, in InsetsVisibilities requestedVisibilities, String packageName,
+            int behavior, int requestedVisibleTypes, String packageName,
             in LetterboxDetails[] letterboxDetails);
 
     /**
diff --git a/core/java/com/android/internal/statusbar/IStatusBarService.aidl b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
index ef8f2db..d190681 100644
--- a/core/java/com/android/internal/statusbar/IStatusBarService.aidl
+++ b/core/java/com/android/internal/statusbar/IStatusBarService.aidl
@@ -23,7 +23,7 @@
 import android.hardware.biometrics.IBiometricContextListener;
 import android.hardware.biometrics.IBiometricSysuiReceiver;
 import android.hardware.biometrics.PromptInfo;
-import android.hardware.fingerprint.IUdfpsHbmListener;
+import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
 import android.media.INearbyMediaDevicesProvider;
 import android.media.MediaRoute2Info;
 import android.net.Uri;
@@ -136,9 +136,9 @@
     void setBiometicContextListener(in IBiometricContextListener listener);
 
     /**
-     * Sets an instance of IUdfpsHbmListener for UdfpsController.
+     * Sets an instance of IUdfpsRefreshRateRequestCallback for UdfpsController.
      */
-    void setUdfpsHbmListener(in IUdfpsHbmListener listener);
+    void setUdfpsRefreshRateCallback(in IUdfpsRefreshRateRequestCallback callback);
 
     /**
      * Show a warning that the device is about to go to sleep due to user inactivity.
diff --git a/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java b/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java
index 8b898f0..54221ce 100644
--- a/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java
+++ b/core/java/com/android/internal/statusbar/RegisterStatusBarResult.java
@@ -21,7 +21,6 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.util.ArrayMap;
-import android.view.InsetsVisibilities;
 
 import com.android.internal.view.AppearanceRegion;
 
@@ -40,7 +39,7 @@
     public final IBinder mImeToken;
     public final boolean mNavbarColorManagedByIme;
     public final int mBehavior;
-    public final InsetsVisibilities mRequestedVisibilities;
+    public final int mRequestedVisibleTypes;
     public final String mPackageName;
     public final int[] mTransientBarTypes;
     public final LetterboxDetails[] mLetterboxDetails;
@@ -48,7 +47,7 @@
     public RegisterStatusBarResult(ArrayMap<String, StatusBarIcon> icons, int disabledFlags1,
             int appearance, AppearanceRegion[] appearanceRegions, int imeWindowVis,
             int imeBackDisposition, boolean showImeSwitcher, int disabledFlags2, IBinder imeToken,
-            boolean navbarColorManagedByIme, int behavior, InsetsVisibilities requestedVisibilities,
+            boolean navbarColorManagedByIme, int behavior, int requestedVisibleTypes,
             String packageName, @NonNull int[] transientBarTypes,
             LetterboxDetails[] letterboxDetails) {
         mIcons = new ArrayMap<>(icons);
@@ -62,7 +61,7 @@
         mImeToken = imeToken;
         mNavbarColorManagedByIme = navbarColorManagedByIme;
         mBehavior = behavior;
-        mRequestedVisibilities = requestedVisibilities;
+        mRequestedVisibleTypes = requestedVisibleTypes;
         mPackageName = packageName;
         mTransientBarTypes = transientBarTypes;
         mLetterboxDetails = letterboxDetails;
@@ -86,7 +85,7 @@
         dest.writeStrongBinder(mImeToken);
         dest.writeBoolean(mNavbarColorManagedByIme);
         dest.writeInt(mBehavior);
-        dest.writeTypedObject(mRequestedVisibilities, 0);
+        dest.writeInt(mRequestedVisibleTypes);
         dest.writeString(mPackageName);
         dest.writeIntArray(mTransientBarTypes);
         dest.writeParcelableArray(mLetterboxDetails, flags);
@@ -112,8 +111,7 @@
                     final IBinder imeToken = source.readStrongBinder();
                     final boolean navbarColorManagedByIme = source.readBoolean();
                     final int behavior = source.readInt();
-                    final InsetsVisibilities requestedVisibilities =
-                            source.readTypedObject(InsetsVisibilities.CREATOR);
+                    final int requestedVisibleTypes = source.readInt();
                     final String packageName = source.readString();
                     final int[] transientBarTypes = source.createIntArray();
                     final LetterboxDetails[] letterboxDetails =
@@ -121,7 +119,7 @@
                     return new RegisterStatusBarResult(icons, disabledFlags1, appearance,
                             appearanceRegions, imeWindowVis, imeBackDisposition, showImeSwitcher,
                             disabledFlags2, imeToken, navbarColorManagedByIme, behavior,
-                            requestedVisibilities, packageName, transientBarTypes,
+                            requestedVisibleTypes, packageName, transientBarTypes,
                             letterboxDetails);
                 }
 
diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl
index 423642a..40c6a05 100644
--- a/core/java/com/android/internal/view/IInputMethodManager.aidl
+++ b/core/java/com/android/internal/view/IInputMethodManager.aidl
@@ -81,8 +81,7 @@
     @EnforcePermission("WRITE_SECURE_SETTINGS")
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
             + "android.Manifest.permission.WRITE_SECURE_SETTINGS)")
-    void showInputMethodPickerFromSystem(in IInputMethodClient client,
-            int auxiliarySubtypeMode, int displayId);
+    void showInputMethodPickerFromSystem(int auxiliarySubtypeMode, int displayId);
 
     @EnforcePermission("TEST_INPUT_METHOD")
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
@@ -147,9 +146,9 @@
     boolean isStylusHandwritingAvailableAsUser(int userId);
 
     /** add virtual stylus id for test Stylus handwriting session **/
-    @EnforcePermission("INJECT_EVENTS")
+    @EnforcePermission("TEST_INPUT_METHOD")
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
-            + "android.Manifest.permission.INJECT_EVENTS)")
+            + "android.Manifest.permission.TEST_INPUT_METHOD)")
     void addVirtualStylusIdForTestSession(in IInputMethodClient client);
 
     /** Set a stylus idle-timeout after which handwriting {@code InkWindow} will be removed. */
diff --git a/core/jni/Android.bp b/core/jni/Android.bp
index d59a51a..cc3d906 100644
--- a/core/jni/Android.bp
+++ b/core/jni/Android.bp
@@ -34,6 +34,8 @@
         "-Wno-error=deprecated-declarations",
         "-Wunused",
         "-Wunreachable-code",
+
+        "-DNAMESPACE_FOR_HASH_FUNCTIONS=farmhash",
     ],
 
     cppflags: ["-Wno-conversion-null"],
@@ -212,6 +214,7 @@
                 "android_content_res_ResourceTimer.cpp",
                 "android_security_Scrypt.cpp",
                 "com_android_internal_content_om_OverlayConfig.cpp",
+                "com_android_internal_expresslog_Counter.cpp",
                 "com_android_internal_net_NetworkUtilsInternal.cpp",
                 "com_android_internal_os_ClassLoaderFactory.cpp",
                 "com_android_internal_os_FuseAppLoop.cpp",
@@ -247,6 +250,7 @@
                 "libscrypt_static",
                 "libstatssocket_lazy",
                 "libskia",
+                "libtextclassifier_hash_static",
             ],
 
             shared_libs: [
@@ -329,7 +333,7 @@
             header_libs: [
                 "bionic_libc_platform_headers",
                 "dnsproxyd_protocol_headers",
-                "libandroid_runtime_vm_headers",
+                "libtextclassifier_hash_headers",
             ],
         },
         host: {
@@ -418,24 +422,3 @@
         never: true,
     },
 }
-
-cc_library_headers {
-    name: "libandroid_runtime_vm_headers",
-    host_supported: true,
-    vendor_available: true,
-    // TODO(b/153609531): remove when libbinder is not native_bridge_supported
-    native_bridge_supported: true,
-    // Allow only modules from the following list to create threads that can be
-    // attached to the JVM. This list should be a subset of the dependencies of
-    // libandroid_runtime.
-    visibility: [
-        "//frameworks/native/libs/binder",
-    ],
-    export_include_dirs: ["include_vm"],
-    header_libs: [
-        "jni_headers",
-    ],
-    export_header_lib_headers: [
-        "jni_headers",
-    ],
-}
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 422bdc9..d9ca16e 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -22,7 +22,6 @@
 #include <android-base/properties.h>
 #include <android/graphics/jni_runtime.h>
 #include <android_runtime/AndroidRuntime.h>
-#include <android_runtime/vm.h>
 #include <assert.h>
 #include <binder/IBinder.h>
 #include <binder/IPCThreadState.h>
@@ -195,6 +194,7 @@
 extern int register_com_android_internal_content_F2fsUtils(JNIEnv* env);
 extern int register_com_android_internal_content_NativeLibraryHelper(JNIEnv *env);
 extern int register_com_android_internal_content_om_OverlayConfig(JNIEnv *env);
+extern int register_com_android_internal_expresslog_Counter(JNIEnv* env);
 extern int register_com_android_internal_net_NetworkUtilsInternal(JNIEnv* env);
 extern int register_com_android_internal_os_ClassLoaderFactory(JNIEnv* env);
 extern int register_com_android_internal_os_FuseAppLoop(JNIEnv* env);
@@ -1576,6 +1576,7 @@
         REG_JNI(register_android_os_SharedMemory),
         REG_JNI(register_android_os_incremental_IncrementalManager),
         REG_JNI(register_com_android_internal_content_om_OverlayConfig),
+        REG_JNI(register_com_android_internal_expresslog_Counter),
         REG_JNI(register_com_android_internal_net_NetworkUtilsInternal),
         REG_JNI(register_com_android_internal_os_ClassLoaderFactory),
         REG_JNI(register_com_android_internal_os_LongArrayMultiStateCounter),
diff --git a/core/jni/android_os_Parcel.cpp b/core/jni/android_os_Parcel.cpp
index 1f64df4..4d8dac1 100644
--- a/core/jni/android_os_Parcel.cpp
+++ b/core/jni/android_os_Parcel.cpp
@@ -116,6 +116,11 @@
     }
 }
 
+static jboolean android_os_Parcel_isForRpc(jlong nativePtr) {
+    Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
+    return parcel ? parcel->isForRpc() : false;
+}
+
 static jint android_os_Parcel_dataSize(jlong nativePtr)
 {
     Parcel* parcel = reinterpret_cast<Parcel*>(nativePtr);
@@ -808,6 +813,8 @@
     // @FastNative
     {"nativeMarkForBinder",       "(JLandroid/os/IBinder;)V", (void*)android_os_Parcel_markForBinder},
     // @CriticalNative
+    {"nativeIsForRpc",            "(J)Z", (void*)android_os_Parcel_isForRpc},
+    // @CriticalNative
     {"nativeDataSize",            "(J)I", (void*)android_os_Parcel_dataSize},
     // @CriticalNative
     {"nativeDataAvail",           "(J)I", (void*)android_os_Parcel_dataAvail},
diff --git a/core/jni/android_view_InputDevice.cpp b/core/jni/android_view_InputDevice.cpp
index 39ec037..b2994f4 100644
--- a/core/jni/android_view_InputDevice.cpp
+++ b/core/jni/android_view_InputDevice.cpp
@@ -70,6 +70,8 @@
                                           deviceInfo.hasMic(), deviceInfo.hasButtonUnderPad(),
                                           deviceInfo.hasSensor(), deviceInfo.hasBattery(),
                                           deviceInfo.supportsUsi()));
+    // Note: We do not populate the Bluetooth address into the InputDevice object to avoid leaking
+    // it to apps that do not have the Bluetooth permission.
 
     const std::vector<InputDeviceInfo::MotionRange>& ranges = deviceInfo.getMotionRanges();
     for (const InputDeviceInfo::MotionRange& range: ranges) {
diff --git a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
index 2cd9f89..6762e45 100644
--- a/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
+++ b/core/jni/com_android_internal_content_NativeLibraryHelper.cpp
@@ -22,6 +22,7 @@
 #include <errno.h>
 #include <fcntl.h>
 #include <inttypes.h>
+#include <linux/fs.h>
 #include <nativehelper/ScopedUtfChars.h>
 #include <stdlib.h>
 #include <string.h>
@@ -250,6 +251,16 @@
         return INSTALL_FAILED_CONTAINER_ERROR;
     }
 
+    // If a filesystem like f2fs supports per-file compression, set the compression bit before data
+    // writes
+    unsigned int flags;
+    if (ioctl(fd, FS_IOC_GETFLAGS, &flags) == -1) {
+        ALOGE("Failed to call FS_IOC_GETFLAGS on %s: %s\n", localTmpFileName, strerror(errno));
+    } else if ((flags & FS_COMPR_FL) == 0) {
+        flags |= FS_COMPR_FL;
+        ioctl(fd, FS_IOC_SETFLAGS, &flags);
+    }
+
     if (!zipFile->uncompressEntry(zipEntry, fd)) {
         ALOGE("Failed uncompressing %s to %s\n", fileName, localTmpFileName);
         close(fd);
diff --git a/core/jni/com_android_internal_expresslog_Counter.cpp b/core/jni/com_android_internal_expresslog_Counter.cpp
new file mode 100644
index 0000000..d4a8c23
--- /dev/null
+++ b/core/jni/com_android_internal_expresslog_Counter.cpp
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <nativehelper/JNIHelp.h>
+#include <utils/hash/farmhash.h>
+
+#include "core_jni_helpers.h"
+
+// ----------------------------------------------------------------------------
+// JNI Glue
+// ----------------------------------------------------------------------------
+
+static jclass g_stringClass = nullptr;
+
+/**
+ * Class:     com_android_internal_expresslog_Counter
+ * Method:    hashString
+ * Signature: (Ljava/lang/String;)J
+ */
+static jlong hashString(JNIEnv* env, jclass /*class*/, jstring metricNameObj) {
+    ScopedUtfChars name(env, metricNameObj);
+    if (name.c_str() == nullptr) {
+        return 0;
+    }
+
+    return static_cast<jlong>(farmhash::Fingerprint64(name.c_str(), name.size()));
+}
+
+static const JNINativeMethod g_methods[] = {
+        {"hashString", "(Ljava/lang/String;)J", (void*)hashString},
+};
+
+static const char* const kCounterPathName = "com/android/internal/expresslog/Counter";
+
+namespace android {
+
+int register_com_android_internal_expresslog_Counter(JNIEnv* env) {
+    jclass stringClass = FindClassOrDie(env, "java/lang/String");
+    g_stringClass = MakeGlobalRefOrDie(env, stringClass);
+
+    return RegisterMethodsOrDie(env, kCounterPathName, g_methods, NELEM(g_methods));
+}
+
+} // namespace android
diff --git a/core/proto/android/server/activitymanagerservice.proto b/core/proto/android/server/activitymanagerservice.proto
index 9e4f63c..4650000 100644
--- a/core/proto/android/server/activitymanagerservice.proto
+++ b/core/proto/android/server/activitymanagerservice.proto
@@ -978,6 +978,11 @@
     }
     repeated UserProfile user_profile_group_ids = 4;
     repeated int32 visible_users_array = 5;
+
+    // current_user contains the id of the current user, while current_profiles contains the ids of
+    // both the current user and its profiles (if any)
+    optional int32 current_user = 6;
+    repeated int32 current_profiles = 7;
 }
 
 // sync with com.android.server.am.AppTimeTracker.java
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 554b153..f0fbff1 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1150,6 +1150,18 @@
                 android:description="@string/permdesc_readMediaImages"
                 android:protectionLevel="dangerous" />
 
+    <!-- Allows an application to read image or video files from external storage that a user has
+      selected via the permission prompt photo picker. Apps can check this permission to verify that
+      a user has decided to use the photo picker, instead of granting access to
+      {@link #READ_MEDIA_IMAGES or #READ_MEDIA_VIDEO}. It does not prevent apps from accessing the
+      standard photo picker manually.
+   <p>Protection level: dangerous -->
+    <permission android:name="android.permission.READ_MEDIA_VISUAL_USER_SELECTED"
+        android:permissionGroup="android.permission-group.UNDEFINED"
+        android:label="@string/permlab_readVisualUserSelect"
+        android:description="@string/permdesc_readVisualUserSelect"
+        android:protectionLevel="dangerous" />
+
     <!-- Allows an application to write to external storage.
          <p><strong>Note: </strong>If your app targets {@link android.os.Build.VERSION_CODES#R} or
          higher, this permission has no effect.
@@ -3152,6 +3164,12 @@
     <permission android:name="android.permission.START_FOREGROUND_SERVICES_FROM_BACKGROUND"
                 android:protectionLevel="signature|privileged|vendorPrivileged|oem|verifier|role"/>
 
+    <!-- Allows an application to hint that a broadcast is associated with an
+         "interactive" usage scenario
+         @hide -->
+    <permission android:name="android.permission.BROADCAST_OPTION_INTERACTIVE"
+                android:protectionLevel="signature|privileged" />
+
     <!-- @SystemApi Must be required by activities that handle the intent action
          {@link Intent#ACTION_SEND_SHOW_SUSPENDED_APP_DETAILS}. This is for use by apps that
          hold {@link Manifest.permission#SUSPEND_APPS} to interact with the system.
@@ -4127,7 +4145,8 @@
         android:protectionLevel="signature" />
 
     <!-- Allows access to Test APIs defined in {@link android.view.inputmethod.InputMethodManager}.
-         @hide -->
+         @hide
+         @TestApi -->
     <permission android:name="android.permission.TEST_INPUT_METHOD"
         android:protectionLevel="signature" />
 
@@ -6622,6 +6641,15 @@
     <permission android:name="android.permission.MANAGE_DEVICE_LOCK_STATE"
                 android:protectionLevel="internal|role" />
 
+    <!-- Allows applications to use the long running jobs APIs.
+         <p>This is a special access permission that can be revoked by the system or the user.
+         <p>Apps need to target API {@link android.os.Build.VERSION_CODES#UPSIDE_DOWN_CAKE} or above
+         to be able to request this permission.
+         <p>Protection level: appop
+     -->
+    <permission android:name="android.permission.RUN_LONG_JOBS"
+                android:protectionLevel="normal|appop"/>
+
     <!-- Attribution for Geofencing service. -->
     <attribution android:tag="GeofencingService" android:label="@string/geofencing_service"/>
     <!-- Attribution for Country Detector. -->
@@ -7183,6 +7211,10 @@
             </intent-filter>
         </service>
 
+        <service android:name="com.android.server.art.BackgroundDexOptJobService"
+                 android:permission="android.permission.BIND_JOB_SERVICE" >
+        </service>
+
         <provider
             android:name="com.android.server.textclassifier.IconsContentProvider"
             android:authorities="com.android.textclassifier.icons"
diff --git a/core/res/res/values-af/strings.xml b/core/res/res/values-af/strings.xml
index 0ebce40..b20bfef 100644
--- a/core/res/res/values-af/strings.xml
+++ b/core/res/res/values-af/strings.xml
@@ -72,6 +72,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Beller-ID se verstek is nie beperk nie. Volgende oproep: nie beperk nie"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Diens nie verskaf nie."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Jy kan nie die beller-ID-instelling verander nie."</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Geen mobiele datadiens nie"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Noodoproepe is onbeskikbaar"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Geen stemdiens nie"</string>
diff --git a/core/res/res/values-am/strings.xml b/core/res/res/values-am/strings.xml
index a861f3c..2574fd8 100644
--- a/core/res/res/values-am/strings.xml
+++ b/core/res/res/values-am/strings.xml
@@ -72,6 +72,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"የደዋይ ID ነባሪዎች ወደአልተከለከለም። ቀጥሎ ጥሪ፡አልተከለከለም"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"አገልግሎት አልቀረበም።"</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"የደዋይ መታወቂያ ቅንብሮች  መለወጥ አትችልም፡፡"</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"ምንም የተንቀሳቃሽ ስልክ ውሂብ አገልግሎት የለም"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"የድንገተኛ አደጋ ጥሪ አይገኝም"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"ምንም የድምፅ ጥሪ አገልግሎት የለም"</string>
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 7623fb1..ba3ebb1 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -76,6 +76,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"الإعداد التلقائي لمعرف المتصل هو غير محظور  . الاتصال التالي: غير محظور"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"الخدمة غير متوفرة."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"لا يمكنك تغيير إعداد معرّف المتصل."</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"لا تتوفّر خدمة بيانات جوّال."</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"لا تتوفّر مكالمات طوارئ."</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"لا تتوفر خدمة صوتية"</string>
diff --git a/core/res/res/values-as/strings.xml b/core/res/res/values-as/strings.xml
index 3a97baf..d49d3b1 100644
--- a/core/res/res/values-as/strings.xml
+++ b/core/res/res/values-as/strings.xml
@@ -72,6 +72,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"কলাৰ আইডি সীমিত নকৰিবলৈ পূর্বনির্ধাৰণ কৰা হৈছে। পৰৱৰ্তী কল: সীমিত কৰা হোৱা নাই"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"সুবিধা যোগান ধৰা হোৱা নাই।"</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"আপুনি কলাৰ আইডি ছেটিং সলনি কৰিব নোৱাৰে।"</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"কোনো ম’বাইল ডেটা সেৱা নাই"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"জৰুৰীকালীন কল কৰাৰ সুবিধা উপলব্ধ নহয়"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"কোনো ভইচ সেৱা নাই"</string>
diff --git a/core/res/res/values-az/strings.xml b/core/res/res/values-az/strings.xml
index 0b361ac..ffd1480 100644
--- a/core/res/res/values-az/strings.xml
+++ b/core/res/res/values-az/strings.xml
@@ -72,6 +72,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Zəng edənin kimliyi defolt olaraq qadağan deyil. Növbəti zəng: Qadağan deyil"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Xidmət təmin edilməyib."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Çağrı kimliyi ayarını dəyişə bilməzsiniz."</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Mobil data xidməti yoxdur"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Təcili zəng əlçatan deyil"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Səsli xidmət yoxdur"</string>
diff --git a/core/res/res/values-b+sr+Latn/strings.xml b/core/res/res/values-b+sr+Latn/strings.xml
index 078c098..b1ec940b 100644
--- a/core/res/res/values-b+sr+Latn/strings.xml
+++ b/core/res/res/values-b+sr+Latn/strings.xml
@@ -73,6 +73,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ID pozivaoca podrazumevano nije ograničen. Sledeći poziv: Nije ograničen."</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Usluga nije dobavljena."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Ne možete da promenite podešavanje ID-a korisnika."</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Nema usluge mobilnih podataka"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Hitni pozivi nisu dostupni"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Nema glasovne usluge"</string>
diff --git a/core/res/res/values-be/strings.xml b/core/res/res/values-be/strings.xml
index 023e82c..40605c5 100644
--- a/core/res/res/values-be/strings.xml
+++ b/core/res/res/values-be/strings.xml
@@ -74,6 +74,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Налады ідэнтыфікатару АВН па змаўчанні: не абмяжавана. Наступны выклік: не абмежавана"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Служба не прадастаўляецца."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Вы не можаце змяніць налады ідэнтыфікатара абанента, якi тэлефануе."</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Мабільная перадача даных недаступная"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Экстранныя выклікі недаступныя"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Няма сэрвісу галасавых выклікаў"</string>
diff --git a/core/res/res/values-bg/strings.xml b/core/res/res/values-bg/strings.xml
index aaa080a..a1c9d1c 100644
--- a/core/res/res/values-bg/strings.xml
+++ b/core/res/res/values-bg/strings.xml
@@ -72,6 +72,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Стандартната идентификация на повикванията е „разрешено“. За следващото обаждане тя е разрешена."</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Услугата не е обезпечена."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Не можете да променяте настройката за идентификация на обажданията."</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Няма достъп до мобилната услуга за данни"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Няма достъп до спешните обаждания"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Няма услуга за гласови обаждания"</string>
diff --git a/core/res/res/values-bn/strings.xml b/core/res/res/values-bn/strings.xml
index ee1db8e..fd69acd 100644
--- a/core/res/res/values-bn/strings.xml
+++ b/core/res/res/values-bn/strings.xml
@@ -72,6 +72,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ডিফল্টরূপে কলার আইডি সীমাবদ্ধ করা থাকে না৷ পরবর্তী কল: সীমাবদ্ধ নয়"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"পরিষেবা প্রস্তুত নয়৷"</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"আপনি কলার আইডি এর সেটিংস পরিবর্তন করতে পারবেন না৷"</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"মোবাইল ডেটা পরিষেবা নেই"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"জরুরি কল করা যাবে না"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"ভয়েস পরিষেবা নেই"</string>
diff --git a/core/res/res/values-bs/strings.xml b/core/res/res/values-bs/strings.xml
index 20f6bc1..455c9e9 100644
--- a/core/res/res/values-bs/strings.xml
+++ b/core/res/res/values-bs/strings.xml
@@ -73,6 +73,8 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Prikaz ID-a pozivaoca u zadanim postavkama nije zabranjen. Sljedeći poziv: nije zabranjen"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Uslugu nije moguće koristiti."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Ne možete promijeniti postavke ID-a pozivaoca."</string>
+    <string name="auto_data_switch_title" msgid="3286350716870518297">"Podaci su prebačeni na <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+    <string name="auto_data_switch_content" msgid="803557715007110959">"To uvijek možete promijeniti u postavkama"</string>
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Nema usluge prijenosa podataka na mobilnoj mreži"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Hitni pozivi su nedostupni"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Nema usluge govornih poziva"</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index 2142b60..43dc676 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -72,6 +72,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"El valor predeterminat de l\'identificador de trucada és no restringit. Trucada següent: no restringit"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"No s\'ha proveït el servei."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"No pots canviar la configuració de l\'identificador de trucada."</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"No hi ha servei de dades mòbils"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Les trucades d\'emergència no estan disponibles"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Sense servei de veu"</string>
diff --git a/core/res/res/values-cs/strings.xml b/core/res/res/values-cs/strings.xml
index 7720d08..132a9f5 100644
--- a/core/res/res/values-cs/strings.xml
+++ b/core/res/res/values-cs/strings.xml
@@ -74,6 +74,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Ve výchozím nastavení není funkce ID volajícího omezena. Příští hovor: Neomezeno"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Služba není zřízena."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Nastavení ID volajícího nesmíte měnit."</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Není k dispozici žádná mobilní datová služba"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Tísňová volání jsou nedostupná"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Hlasová volání nejsou k dispozici"</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index ecd6407..9dd4334 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -72,6 +72,8 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Standarder for opkalds-id til ikke begrænset. Næste opkald: Ikke begrænset"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Tjenesten provisioneres ikke."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Du kan ikke ændre indstillingen for opkalds-id\'et."</string>
+    <string name="auto_data_switch_title" msgid="3286350716870518297">"Der blev skiftet til <xliff:g id="CARRIERDISPLAY">%s</xliff:g>-data"</string>
+    <string name="auto_data_switch_content" msgid="803557715007110959">"Du kan altid ændre dette under Indstillinger"</string>
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Ingen mobildatatjeneste"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Det er ikke muligt at foretage nødopkald"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Ingen taletjeneste"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index 3d5985c9..661c2cd 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -72,6 +72,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Anrufer-ID ist standardmäßig nicht beschränkt. Nächster Anruf: Nicht beschränkt"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Dienst nicht eingerichtet."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Du kannst die Einstellung für die Anrufer-ID nicht ändern."</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Kein mobiler Datendienst"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Notrufe nicht möglich"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Keine Anrufe"</string>
diff --git a/core/res/res/values-el/strings.xml b/core/res/res/values-el/strings.xml
index f84a9fb..8cb3989b 100644
--- a/core/res/res/values-el/strings.xml
+++ b/core/res/res/values-el/strings.xml
@@ -72,6 +72,8 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Η αναγνώριση κλήσης βρίσκεται από προεπιλογή στην \"μη περιορισμένη\". Επόμενη κλήση: Μη περιορισμένη"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Η υπηρεσία δεν προβλέπεται."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Δεν μπορείτε να αλλάξετε τη ρύθμιση του αναγνωριστικού καλούντος."</string>
+    <string name="auto_data_switch_title" msgid="3286350716870518297">"Έγινε εναλλαγή των δεδομένων σε <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+    <string name="auto_data_switch_content" msgid="803557715007110959">"Μπορείτε να αλλάξετε αυτήν την επιλογή ανά πάσα στιγμή στις Ρυθμίσεις"</string>
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Δεν υπάρχει υπηρεσία δεδομένων κινητής τηλεφωνίας"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Οι κλήσεις έκτακτης ανάγκης δεν είναι διαθέσιμες"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Δεν υπάρχει φωνητική υπηρεσία"</string>
diff --git a/core/res/res/values-en-rAU/strings.xml b/core/res/res/values-en-rAU/strings.xml
index 4c0510b..75db3826 100644
--- a/core/res/res/values-en-rAU/strings.xml
+++ b/core/res/res/values-en-rAU/strings.xml
@@ -72,6 +72,8 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Caller ID defaults to not restricted. Next call: Not restricted"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Service not provisioned."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"You can\'t change the caller ID setting."</string>
+    <string name="auto_data_switch_title" msgid="3286350716870518297">"Switched data to <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+    <string name="auto_data_switch_content" msgid="803557715007110959">"You can change this at any time in Settings"</string>
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"No mobile data service"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Emergency calling unavailable"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"No voice service"</string>
diff --git a/core/res/res/values-en-rCA/strings.xml b/core/res/res/values-en-rCA/strings.xml
index 875ddf9..883cd55 100644
--- a/core/res/res/values-en-rCA/strings.xml
+++ b/core/res/res/values-en-rCA/strings.xml
@@ -72,6 +72,8 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Caller ID defaults to not restricted. Next call: Not restricted"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Service not provisioned."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"You can\'t change the caller ID setting."</string>
+    <string name="auto_data_switch_title" msgid="3286350716870518297">"Switched data to <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+    <string name="auto_data_switch_content" msgid="803557715007110959">"You can change this at any time in Settings"</string>
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"No mobile data service"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Emergency calling unavailable"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"No voice service"</string>
diff --git a/core/res/res/values-en-rGB/strings.xml b/core/res/res/values-en-rGB/strings.xml
index 6e034b7..c4c19da 100644
--- a/core/res/res/values-en-rGB/strings.xml
+++ b/core/res/res/values-en-rGB/strings.xml
@@ -72,6 +72,8 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Caller ID defaults to not restricted. Next call: Not restricted"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Service not provisioned."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"You can\'t change the caller ID setting."</string>
+    <string name="auto_data_switch_title" msgid="3286350716870518297">"Switched data to <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+    <string name="auto_data_switch_content" msgid="803557715007110959">"You can change this at any time in Settings"</string>
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"No mobile data service"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Emergency calling unavailable"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"No voice service"</string>
diff --git a/core/res/res/values-en-rIN/strings.xml b/core/res/res/values-en-rIN/strings.xml
index 643f27f..d19199d 100644
--- a/core/res/res/values-en-rIN/strings.xml
+++ b/core/res/res/values-en-rIN/strings.xml
@@ -72,6 +72,8 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Caller ID defaults to not restricted. Next call: Not restricted"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Service not provisioned."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"You can\'t change the caller ID setting."</string>
+    <string name="auto_data_switch_title" msgid="3286350716870518297">"Switched data to <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+    <string name="auto_data_switch_content" msgid="803557715007110959">"You can change this at any time in Settings"</string>
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"No mobile data service"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Emergency calling unavailable"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"No voice service"</string>
diff --git a/core/res/res/values-en-rXC/strings.xml b/core/res/res/values-en-rXC/strings.xml
index 91e99ff..9037d70 100644
--- a/core/res/res/values-en-rXC/strings.xml
+++ b/core/res/res/values-en-rXC/strings.xml
@@ -72,6 +72,8 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‏‎‏‎‎‏‎‎‏‏‏‏‎‏‏‎‏‏‎‏‎‏‎‎‏‏‏‏‏‎‏‏‎‎‎‎‏‎‎‎‏‎‎‏‏‎‏‎‏‏‎‏‎‏‎‎‏‏‎Caller ID defaults to not restricted. Next call: Not restricted‎‏‎‎‏‎"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‏‎‎‎‎‏‎‎‏‏‎‏‎‎‎‎‎‏‏‏‎‏‎‎‎‏‎‏‎‎‏‏‎‎‏‏‎‎‏‏‏‎‏‏‏‏‏‏‏‏‏‏‏‎‎‎‏‎Service not provisioned.‎‏‎‎‏‎"</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‎‏‎‎‏‎‎‏‏‏‏‎‏‎‏‎‎‏‏‏‎‏‎‎‏‎‏‏‎‎‏‎‏‎‏‎‏‎‏‏‎‎‏‏‎‎‎‏‏‎‏‏‎‏‏‏‏‏‎‎You can\'t change the caller ID setting.‎‏‎‎‏‎"</string>
+    <string name="auto_data_switch_title" msgid="3286350716870518297">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‎‏‏‎‎‏‏‎‏‏‎‏‏‏‎‏‏‎‏‎‎‏‎‎‎‎‏‎‏‎‏‏‏‎‎‏‏‎‏‎‏‎‏‏‏‎‎‏‏‎‎‎‎‏‏‎‎‏‎Switched data to ‎‏‎‎‏‏‎<xliff:g id="CARRIERDISPLAY">%s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
+    <string name="auto_data_switch_content" msgid="803557715007110959">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‏‎‎‏‎‎‏‏‎‏‏‎‎‏‏‏‏‎‏‏‏‏‎‏‏‏‏‏‏‏‏‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‏‏‏‎‎‏‎‏‏‏‏‎You can change this anytime in Settings‎‏‎‎‏‎"</string>
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‎‏‏‎‏‎‎‏‏‎‎‎‏‏‏‏‎‎‏‎‎‏‏‎‏‎‎‎‏‎‏‎‏‏‎‎‏‏‎‎‎‏‏‏‏‏‏‎‎‎‎‏‎‏‏‏‎‎No mobile data service‎‏‎‎‏‎"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‏‏‏‎‎‏‎‏‏‏‏‎‎‏‏‎‎‎‏‏‎‎‏‏‏‏‏‎‎‎‏‎‏‎‎‎‏‎‏‎‏‎‎‏‏‏‏‏‏‏‏‏‏‏‏‎‏‎‎Emergency calling unavailable‎‏‎‎‏‎"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‎‎‎‏‎‏‎‎‎‏‏‎‏‎‏‎‏‎‎‎‎‎‎‎‎‎‎‎‎‏‏‎‎‏‏‏‏‏‏‏‏‎‎‎‎‏‎‏‏‎‏‏‏‎‏‏‏‎‎‏‎No voice service‎‏‎‎‏‎"</string>
diff --git a/core/res/res/values-es-rUS/strings.xml b/core/res/res/values-es-rUS/strings.xml
index 6a45205..9441f27 100644
--- a/core/res/res/values-es-rUS/strings.xml
+++ b/core/res/res/values-es-rUS/strings.xml
@@ -73,6 +73,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"El Identificador de llamadas está predeterminado en no restringido. Llamada siguiente: no restringido"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Servicio no suministrado."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"No puedes cambiar la configuración del identificador de llamadas."</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"No hay ningún servicio de datos móviles"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Servicio de llamadas de emergencia no disponible"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Sin servicio de voz"</string>
diff --git a/core/res/res/values-es/strings.xml b/core/res/res/values-es/strings.xml
index 66f67b3..753aa8b 100644
--- a/core/res/res/values-es/strings.xml
+++ b/core/res/res/values-es/strings.xml
@@ -73,6 +73,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"La identificación del emisor presenta el valor predeterminado de no restringido. Siguiente llamada: No restringido"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"El servicio no se suministra."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"No puedes modificar la identificación de emisor."</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"No hay ningún servicio de datos móviles"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Servicio de llamadas de emergencia no disponible"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Sin servicio de voz"</string>
diff --git a/core/res/res/values-et/strings.xml b/core/res/res/values-et/strings.xml
index 349a6b2..1789793 100644
--- a/core/res/res/values-et/strings.xml
+++ b/core/res/res/values-et/strings.xml
@@ -72,6 +72,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Helistaja ID pole vaikimisi piiratud. Järgmine kõne: pole piiratud"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Teenus pole ette valmistatud."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Helistaja ID seadet ei saa muuta."</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Mobiilne andmesideteenus puudub"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Hädaabikõned pole saadaval"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Häälkõned pole saadaval"</string>
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index d4759d5..8a7d073 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -72,6 +72,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Deitzailearen identitatea zerbitzuaren balio lehenetsiak ez du murriztapenik ezartzen. Hurrengo deia: murriztapenik gabe."</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Zerbitzua ez da hornitu."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Ezin duzu aldatu deitzailearen identitatearen ezarpena."</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Ez dago mugikorreko datu-zerbitzurik"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Ezin da egin larrialdi-deirik"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Ez dago ahots-deien zerbitzurik"</string>
diff --git a/core/res/res/values-fa/strings.xml b/core/res/res/values-fa/strings.xml
index 4064353..88bbcc6 100644
--- a/core/res/res/values-fa/strings.xml
+++ b/core/res/res/values-fa/strings.xml
@@ -72,6 +72,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"پیش‌فرض شناسه تماس‌گیرنده روی غیرمحدود است. تماس بعدی: بدون محدودیت"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"سرویس دارای مجوز نیست."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"‏شما می‎توانید تنظیم شناسه تماس‌گیرنده را تغییر دهید."</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"بدون سرویس داده تلفن همراه"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"تماس اضطراری دردسترس نیست"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"سرویس صوتی دردسترس نیست"</string>
diff --git a/core/res/res/values-fi/strings.xml b/core/res/res/values-fi/strings.xml
index 8fedfb7..da3a91e 100644
--- a/core/res/res/values-fi/strings.xml
+++ b/core/res/res/values-fi/strings.xml
@@ -72,6 +72,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Soittajan tunnukseksi muutetaan rajoittamaton. Seuraava puhelu: ei rajoitettu"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Palvelua ei tarjota."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Et voi muuttaa soittajan tunnuksen asetusta."</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Ei mobiilidatapalvelua"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Hätäpuhelut eivät ole käytettävissä"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Ei äänipuheluja"</string>
diff --git a/core/res/res/values-fr-rCA/strings.xml b/core/res/res/values-fr-rCA/strings.xml
index e63b734..d05d97b 100644
--- a/core/res/res/values-fr-rCA/strings.xml
+++ b/core/res/res/values-fr-rCA/strings.xml
@@ -73,6 +73,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Par défaut, les numéros des appelants ne sont pas restreints. Appel suivant : non restreint"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Ce service n\'est pas pris en charge."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Impossible de modifier le paramètre relatif au numéro de l\'appelant."</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Aucun service de données cellulaires"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Le service d\'appel d\'urgence n\'est pas accessible"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Aucun service vocal"</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index 019fdf2..cb2e201 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -73,6 +73,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Par défaut, les numéros des appelants ne sont pas restreints. Appel suivant : non restreint"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Ce service n\'est pas pris en charge."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Impossible de modifier le paramètre relatif au numéro de l\'appelant."</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Aucun service de données mobiles"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Appels d\'urgence non disponibles"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Aucun service vocal"</string>
diff --git a/core/res/res/values-gl/strings.xml b/core/res/res/values-gl/strings.xml
index 219299f..a3aed41 100644
--- a/core/res/res/values-gl/strings.xml
+++ b/core/res/res/values-gl/strings.xml
@@ -72,6 +72,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"O valor predeterminado do identificador de chamada é restrinxido. Próxima chamada: non restrinxido"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Servizo non ofrecido."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Non podes cambiar a configuración do identificador de chamada."</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Non hai servizo de datos para móbiles"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"As chamadas de emerxencia non están dispoñibles"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Non hai servizo de chamadas de voz"</string>
diff --git a/core/res/res/values-gu/strings.xml b/core/res/res/values-gu/strings.xml
index 90dda1a..2d9210b 100644
--- a/core/res/res/values-gu/strings.xml
+++ b/core/res/res/values-gu/strings.xml
@@ -72,6 +72,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"કૉલર ID પ્રતિબંધિત નહીં પર ડિફોલ્ટ છે. આગલો કૉલ: પ્રતિબંધિત નહીં"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"સેવાની જોગવાઈ કરી નથી."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"તમે કૉલર ID સેટિંગ બદલી શકતાં નથી."</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"કોઈ મોબાઇલ ડેટા સેવા નથી"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"કોટોકટીની કૉલિંગ સેવા અનુપલબ્ધ"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"કોઈ વૉઇસ સેવા નથી"</string>
diff --git a/core/res/res/values-hi/strings.xml b/core/res/res/values-hi/strings.xml
index af5bc1f..9f0e936 100644
--- a/core/res/res/values-hi/strings.xml
+++ b/core/res/res/values-hi/strings.xml
@@ -72,6 +72,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"कॉलर आईडी डिफ़ॉल्ट रूप से सीमित नहीं है. अगली कॉल: सीमित नहीं"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"सेवा प्रावधान की हुई नहीं है."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"आप कॉलर आईडी सेटिंग नहीं बदल सकते."</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"मोबाइल डेटा सेवा पर रोक लगा दी गई है"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"आपातकालीन कॉल पर रोक लगा दी गई है"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"कोई वॉइस सेवा नहीं है"</string>
diff --git a/core/res/res/values-hr/strings.xml b/core/res/res/values-hr/strings.xml
index 87df29a..40a49db 100644
--- a/core/res/res/values-hr/strings.xml
+++ b/core/res/res/values-hr/strings.xml
@@ -73,6 +73,8 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Zadana postavka ID-a pozivatelja nema ograničenje. Sljedeći poziv: Nije ograničen"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Usluga nije rezervirana."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Ne možete promijeniti postavku ID-a pozivatelja."</string>
+    <string name="auto_data_switch_title" msgid="3286350716870518297">"Podaci su prebačeni na <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+    <string name="auto_data_switch_content" msgid="803557715007110959">"To uvijek možete promijeniti u postavkama"</string>
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Nema podatkovne mobilne usluge"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Hitni pozivi nisu dostupni"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Nema glasovnih usluga"</string>
diff --git a/core/res/res/values-hu/strings.xml b/core/res/res/values-hu/strings.xml
index 3762fde..cf6132f 100644
--- a/core/res/res/values-hu/strings.xml
+++ b/core/res/res/values-hu/strings.xml
@@ -72,6 +72,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"A hívóazonosító alapértelmezett értéke nem korlátozott. Következő hívás: nem korlátozott"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"A szolgáltatás nincs biztosítva."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Nem tudja módosítani a hívó fél azonosítója beállítást."</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Nincs mobiladat-szolgáltatás"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Segélyhívás nem lehetséges"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Hangszolgáltatás letiltva"</string>
diff --git a/core/res/res/values-hy/strings.xml b/core/res/res/values-hy/strings.xml
index a11e24b..4da9e3b 100644
--- a/core/res/res/values-hy/strings.xml
+++ b/core/res/res/values-hy/strings.xml
@@ -72,6 +72,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Զանգողի ID-ն լռելյայն չսահմանափակված է: Հաջորդ զանգը` չսահմանափակված"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Ծառայությունը չի տրամադրվում:"</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Դուք չեք կարող փոխել զանգողի ID-ի կարգավորումները:"</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Բջջային ինտերնետի ծառայությունն արգելափակված է"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Շտապ կանչերը հասանելի չեն"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Ձայնային ծառայությունն անհասանելի է"</string>
diff --git a/core/res/res/values-in/strings.xml b/core/res/res/values-in/strings.xml
index dbccee9..2f2e524 100644
--- a/core/res/res/values-in/strings.xml
+++ b/core/res/res/values-in/strings.xml
@@ -72,6 +72,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ID penelepon diatur default ke tidak dibatasi. Panggilan selanjutnya: Tidak dibatasi"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Layanan tidak diperlengkapi."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Anda tidak dapat mengubah setelan ID penelepon."</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Tidak ada layanan data seluler"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Panggilan darurat tidak tersedia"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Tidak ada layanan panggilan suara"</string>
diff --git a/core/res/res/values-is/strings.xml b/core/res/res/values-is/strings.xml
index cfefc03..82a1b66 100644
--- a/core/res/res/values-is/strings.xml
+++ b/core/res/res/values-is/strings.xml
@@ -72,6 +72,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Númerabirting er sjálfgefið án takmarkana. Næsta símtal: Án takmarkana"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Þjónustu ekki útdeilt."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Þú getur ekki breytt stillingu númerabirtingar."</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Engin gagnaþjónusta fyrir farsíma"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Neyðarsímtöl eru ekki í boði"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Símtöl eru ekki í boði"</string>
diff --git a/core/res/res/values-it/strings.xml b/core/res/res/values-it/strings.xml
index b05bf79..b499eea 100644
--- a/core/res/res/values-it/strings.xml
+++ b/core/res/res/values-it/strings.xml
@@ -73,6 +73,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ID chiamante generalmente non limitato. Prossima chiamata: non limitato"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Servizio non fornito."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Non è possibile modificare l\'impostazione ID chiamante."</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Nessun servizio dati mobile"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Chiamate di emergenza non disponibili"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Nessun servizio di telefonia"</string>
diff --git a/core/res/res/values-iw/strings.xml b/core/res/res/values-iw/strings.xml
index 8656fce..2ef4934 100644
--- a/core/res/res/values-iw/strings.xml
+++ b/core/res/res/values-iw/strings.xml
@@ -74,6 +74,8 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"זיהוי מתקשר עובר כברירת מחדל למצב לא מוגבל. השיחה הבאה: לא מוגבלת"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"השירות לא הוקצה."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"אינך יכול לשנות את הגדרת זיהוי המתקשר."</string>
+    <string name="auto_data_switch_title" msgid="3286350716870518297">"הנתונים עברו אל <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+    <string name="auto_data_switch_content" msgid="803557715007110959">"תמיד אפשר לשנות זאת ב\'הגדרות\'"</string>
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"אין שירות של חבילת גלישה"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"שיחות חירום לא זמינות"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"אין אפשרות לבצע שיחות רגילות"</string>
diff --git a/core/res/res/values-ja/strings.xml b/core/res/res/values-ja/strings.xml
index 69d0b9d..53fa057 100644
--- a/core/res/res/values-ja/strings.xml
+++ b/core/res/res/values-ja/strings.xml
@@ -72,6 +72,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"既定: 発信者番号通知、次の発信: 通知"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"提供可能なサービスがありません。"</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"発信者番号の設定は変更できません。"</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"モバイルデータ サービスのブロック"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"緊急通報のブロック"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"音声通話サービス停止"</string>
diff --git a/core/res/res/values-ka/strings.xml b/core/res/res/values-ka/strings.xml
index 6d32f25..57ff75a 100644
--- a/core/res/res/values-ka/strings.xml
+++ b/core/res/res/values-ka/strings.xml
@@ -72,6 +72,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ნაგულისხმებად დაყენებულია ნომრის დაფარვის გამორთვა. შემდეგი ზარი: არ არის დაფარული."</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"სერვისი არ არის მიწოდებული."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"არ შეგიძლიათ აბონენტის ID პარამეტრების შეცვლა."</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"მობილური ინტერნეტის სერვისი არ არის"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"გადაუდებელი ზარი მიუწვდომელია"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"ხმოვანი ზარების სერვისი არ არის"</string>
diff --git a/core/res/res/values-kk/strings.xml b/core/res/res/values-kk/strings.xml
index 0d9fd3f..8f5b1c0 100644
--- a/core/res/res/values-kk/strings.xml
+++ b/core/res/res/values-kk/strings.xml
@@ -72,6 +72,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Қоңырау шалушының жеке анықтағышы бастапқы бойынша шектелмеген. Келесі қоңырау: Шектелмеген"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Қызмет ұсынылмаған."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Қоңырау шалушы идентификаторы параметрін өзгерту мүмкін емес."</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Мобильдік интернет қызметі жоқ"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Жедел қызметке қоңырау шалу қолжетімді емес"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Дауыстық қоңыраулар қызметі жоқ"</string>
diff --git a/core/res/res/values-km/strings.xml b/core/res/res/values-km/strings.xml
index 0c82b66..1eae9db 100644
--- a/core/res/res/values-km/strings.xml
+++ b/core/res/res/values-km/strings.xml
@@ -72,6 +72,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"មិន​បាន​ដាក់កម្រិត​លំនាំដើម​លេខ​សម្គាល់​អ្នក​ហៅ។ ការ​ហៅ​បន្ទាប់៖ មិន​បាន​ដាក់​កម្រិត។"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"មិន​បាន​ផ្ដល់​សេវាកម្ម។"</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"អ្នក​មិន​អាច​ប្ដូរ​ការ​កំណត់​លេខ​សម្គាល់​អ្នក​ហៅ​បានទេ។"</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"គ្មាន​​សេវាកម្ម​ទិន្នន័យ​ចល័ត​ទេ"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"ការ​ហៅ​បន្ទាន់​មិន​អាច​ប្រើ​បាន​ទេ"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"គ្មាន​សេវាកម្ម​ជា​សំឡេង​ទេ"</string>
diff --git a/core/res/res/values-kn/strings.xml b/core/res/res/values-kn/strings.xml
index e27527f..821138a 100644
--- a/core/res/res/values-kn/strings.xml
+++ b/core/res/res/values-kn/strings.xml
@@ -72,6 +72,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ಕರೆಮಾಡುವವರ ID ಅನ್ನು ನಿರ್ಬಂಧಿಸದಿರುವಂತೆ ಡಿಫಾಲ್ಟ್ ಮಾಡಲಾಗಿದೆ. ಮುಂದಿನ ಕರೆ: ನಿರ್ಬಂಧಿಸಲಾಗಿಲ್ಲ"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"ಸೇವೆಯನ್ನು ಪೂರೈಸಲಾಗಿಲ್ಲ."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"ನೀವು ಕಾಲರ್‌ ID ಸೆಟ್ಟಿಂಗ್‌ ಬದಲಾಯಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ."</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"ಮೊಬೈಲ್ ಡೇಟಾ ಸೇವೆಯಿಲ್ಲ"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"ತುರ್ತು ಕರೆ ಲಭ್ಯವಿಲ್ಲ"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"ಧ್ವನಿ ಸೇವೆಯಿಲ್ಲ"</string>
diff --git a/core/res/res/values-ko/strings.xml b/core/res/res/values-ko/strings.xml
index c953a39..5f3c7ca 100644
--- a/core/res/res/values-ko/strings.xml
+++ b/core/res/res/values-ko/strings.xml
@@ -72,6 +72,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"발신자 번호가 기본적으로 제한되지 않음으로 설정됩니다. 다음 통화: 제한되지 않음"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"서비스가 준비되지 않았습니다."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"발신자 번호 설정을 변경할 수 없습니다."</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"모바일 데이터 서비스가 차단됨"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"긴급 전화를 사용할 수 없음"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"음성 서비스를 이용할 수 없음"</string>
diff --git a/core/res/res/values-ky/strings.xml b/core/res/res/values-ky/strings.xml
index dccc4a6..6e7b355 100644
--- a/core/res/res/values-ky/strings.xml
+++ b/core/res/res/values-ky/strings.xml
@@ -72,6 +72,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Номурду аныктоонун демейки абалы \"чектелбейт\" деп коюлган. Кийинки чалуу: Чектелбейт"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Кызмат камсыздалган эмес."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Чалуучунун далдаштырма дайындары жөндөөлөрүн өзгөртө албайсыз."</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Мобилдик Интернет кызматы жок"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Шашылыш чалуу бөгөттөлгөн"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Аудио чалуу кызматы бөгөттөлгөн"</string>
diff --git a/core/res/res/values-lo/strings.xml b/core/res/res/values-lo/strings.xml
index c6524de..7a76c2c 100644
--- a/core/res/res/values-lo/strings.xml
+++ b/core/res/res/values-lo/strings.xml
@@ -72,6 +72,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ໝາຍເລກຜູ່ໂທ ໄດ້ຮັບການຕັ້ງຄ່າເລີ່ມຕົ້ນເປັນ ບໍ່ຖືກຈຳກັດ. ການໂທຄັ້ງຕໍ່ໄປ: ບໍ່ຖືກຈຳກັດ."</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"ບໍ່ໄດ້ເປີດໃຊ້ບໍລິການ."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"ທ່ານບໍ່ສາມາດປ່ຽນແປງການຕັ້ງຄ່າ Caller ID"</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"ບໍ່ມີບໍລິການອິນເຕີເນັດມືຖື"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"ບໍ່ສາມາດໃຊ້ການໂທສຸກເສີນໄດ້"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"ບໍ່ມີບໍລິການໂທສຽງ"</string>
diff --git a/core/res/res/values-lt/strings.xml b/core/res/res/values-lt/strings.xml
index adf30e8..e0d1fc7 100644
--- a/core/res/res/values-lt/strings.xml
+++ b/core/res/res/values-lt/strings.xml
@@ -74,6 +74,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Skambintojo ID pagal numatytuosius nustatymus yra neapribotas. Kitas skambutis: neapribotas"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Paslauga neteikiama."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Negalima pakeisti skambinančiojo ID nustatymo."</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Duomenų paslaugos mobiliesiems nėra"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Skambučių pagalbos numeriu paslaugos nėra"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Balso skambučių paslauga neteikiama"</string>
diff --git a/core/res/res/values-lv/strings.xml b/core/res/res/values-lv/strings.xml
index 5631521..ec8cb7d 100644
--- a/core/res/res/values-lv/strings.xml
+++ b/core/res/res/values-lv/strings.xml
@@ -73,6 +73,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Zvanītāja ID noklusējumi ir iestatīti uz Nav ierobežots. Nākamais zvans: nav ierobežots"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Pakalpojums netiek nodrošināts."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Zvanītāja ID iestatījumu nevar mainīt."</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Nav pieejams neviens datu pakalpojums mobilajām ierīcēm"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Nav pieejami ārkārtas izsaukumi"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Balss izsaukumu pakalpojums nedarbojas"</string>
diff --git a/core/res/res/values-mk/strings.xml b/core/res/res/values-mk/strings.xml
index a45d0a7..442047d 100644
--- a/core/res/res/values-mk/strings.xml
+++ b/core/res/res/values-mk/strings.xml
@@ -72,6 +72,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Стандардно, ID на повикувач не е скриен. Следен повик: не е скриен"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Услугата не е предвидена."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Не може да го промените поставувањето за ID на повикувач."</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Нема услуга за мобилен интернет"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Итните повици се недостапни"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Нема услуга за говорни повици"</string>
diff --git a/core/res/res/values-ml/strings.xml b/core/res/res/values-ml/strings.xml
index 492cd54..5b59e66 100644
--- a/core/res/res/values-ml/strings.xml
+++ b/core/res/res/values-ml/strings.xml
@@ -72,6 +72,8 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"നിയന്ത്രിക്കേണ്ടതല്ലാത്ത സ്ഥിര കോളർ ഐഡികൾ. അടുത്ത കോൾ: നിയന്ത്രിച്ചിട്ടില്ല"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"സേവനം വ്യവസ്ഥ ചെയ്‌തിട്ടില്ല."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"വിളിച്ച നമ്പർ ക്രമീകരണം നിങ്ങൾക്ക് മാറ്റാനാവില്ല."</string>
+    <string name="auto_data_switch_title" msgid="3286350716870518297">"<xliff:g id="CARRIERDISPLAY">%s</xliff:g> എന്നതിലേക്ക് ഡാറ്റ മാറ്റി"</string>
+    <string name="auto_data_switch_content" msgid="803557715007110959">"നിങ്ങൾക്ക് ക്രമീകരണത്തിൽ ഏതുസമയത്തും ഇത് മാറ്റാം"</string>
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"മൊബൈൽ ഡാറ്റാ സേവനമില്ല"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"എമർജൻസി കോളിംഗ് ലഭ്യമല്ല"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"വോയ്സ് സേവനമില്ല"</string>
diff --git a/core/res/res/values-mn/strings.xml b/core/res/res/values-mn/strings.xml
index 2c8aaae..03deebb 100644
--- a/core/res/res/values-mn/strings.xml
+++ b/core/res/res/values-mn/strings.xml
@@ -72,6 +72,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Дуудлага хийгчийн ID хязгаарлагдсан. Дараагийн дуудлага: Хязгаарлагдсан"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Үйлчилгээ провишн хийгдээгүй ."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Та дуудлага хийгчийн ID тохиргоог солиж чадахгүй."</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Мобайл дата үйлчилгээ алга"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Яаралтай дуудлага боломжтой"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Дуу хоолойны үйлчилгээ алга"</string>
diff --git a/core/res/res/values-mr/strings.xml b/core/res/res/values-mr/strings.xml
index d47fea35..1d25e67 100644
--- a/core/res/res/values-mr/strings.xml
+++ b/core/res/res/values-mr/strings.xml
@@ -72,6 +72,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"कॉलर आयडी डीफॉल्‍ट रूपात प्रतिबंधित नाही वर सेट असतो. पुढील कॉल: प्रतिबंधित नाही"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"सेवेची तरतूद केलेली नाही."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"तुम्ही कॉलर आयडी सेटिंग बदलू शकत नाही."</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"मोबाइल डेटा सेवा नाही"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"आणीबाणी कॉलिंग अनुपलब्‍ध आहे"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"व्हॉइस सेवा नाही"</string>
diff --git a/core/res/res/values-ms/strings.xml b/core/res/res/values-ms/strings.xml
index deb343d..72d3f2f 100644
--- a/core/res/res/values-ms/strings.xml
+++ b/core/res/res/values-ms/strings.xml
@@ -72,6 +72,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ID pemanggil secara lalainya ditetapkan kepada tidak dihadkan. Panggilan seterusnya: Tidak terhad"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Perkhidmatan yang tidak diuntukkan."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Anda tidak boleh mengubah tetapan ID pemanggil."</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Tiada perkhidmatan data mudah alih"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Panggilan kecemasan tidak tersedia"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Tiada perkhidmatan suara"</string>
diff --git a/core/res/res/values-my/strings.xml b/core/res/res/values-my/strings.xml
index 5f41672..5ff033d 100644
--- a/core/res/res/values-my/strings.xml
+++ b/core/res/res/values-my/strings.xml
@@ -72,6 +72,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ပုံသေအားဖြင့် ခေါ်ဆိုသူအိုင်ဒီ(Caller ID)အား ကန့်သတ်မထားပါ။ နောက်ထပ်အဝင်ခေါ်ဆိုမှု-ကန့်သတ်မထားပါ။"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"ဝန်ဆောင်မှုအား ကန့်သတ်မထားပါ"</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"သင်သည် ခေါ်ဆိုသူ ID ဆက်တင်ကို မပြောင်းလဲနိုင်ပါ။"</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"မိုဘိုင်း ဒေတာဝန်ဆောင်မှု မရှိပါ"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"အရေးပေါ်ခေါ်ဆိုမှု မရနိုင်ပါ"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"ဖုန်းဝန်ဆောင်မှု မရှိပါ"</string>
diff --git a/core/res/res/values-nb/strings.xml b/core/res/res/values-nb/strings.xml
index 0c9a983..bb4fbe4 100644
--- a/core/res/res/values-nb/strings.xml
+++ b/core/res/res/values-nb/strings.xml
@@ -72,6 +72,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Nummervisning er ikke begrenset som standard. Neste anrop: Ikke begrenset"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"SIM-kortet er ikke tilrettelagt for tjenesten."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Du kan ikke endre innstillingen for anrops-ID."</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Ingen mobildatatjeneste"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Nødanrop er utilgjengelig"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Ingen taletjeneste"</string>
diff --git a/core/res/res/values-ne/strings.xml b/core/res/res/values-ne/strings.xml
index e95d48b..ea96df0 100644
--- a/core/res/res/values-ne/strings.xml
+++ b/core/res/res/values-ne/strings.xml
@@ -72,6 +72,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"कलर ID पूर्वनिर्धारितको लागि रोकावट छैन। अर्को कल: रोकावट छैन"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"सेवाको व्यवस्था छैन।"</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"तपाईं कलर ID सेटिङ परिवर्तन गर्न सक्नुहुन्न।"</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"कुनै पनि मोबाइल डेटा सेवा उपलब्ध छैन"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"आपत्‌कालीन कल सेवा उपलब्ध छैन"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"कुनै पनि भ्वाइस सेवा उपलब्ध छैन"</string>
diff --git a/core/res/res/values-nl/strings.xml b/core/res/res/values-nl/strings.xml
index 5723510..24877dd 100644
--- a/core/res/res/values-nl/strings.xml
+++ b/core/res/res/values-nl/strings.xml
@@ -72,6 +72,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Beller-ID standaard ingesteld op \'onbeperkt\'. Volgend gesprek: onbeperkt."</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Service niet voorzien."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"U kunt de instelling voor de beller-ID niet wijzigen."</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Geen service voor mobiele data"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Noodoproepen niet beschikbaar"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Geen belservice"</string>
diff --git a/core/res/res/values-or/strings.xml b/core/res/res/values-or/strings.xml
index c4150dc..456fc83 100644
--- a/core/res/res/values-or/strings.xml
+++ b/core/res/res/values-or/strings.xml
@@ -72,6 +72,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"କଲର୍ ଆଇଡି ଡିଫଲ୍ଟ ଭାବରେ ପ୍ରତିବନ୍ଧିତ ନୁହେଁ। ପରବର୍ତ୍ତୀ କଲ୍: ପ୍ରତିବନ୍ଧିତ ନୁହେଁ"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"ସେବାର ସୁବିଧା ନାହିଁ।"</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"ଆପଣ କଲର୍‍ ID ସେଟିଙ୍ଗ ବଦଳାଇପାରିବେ ନାହିଁ।"</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"କୌଣସି ମୋବାଇଲ୍ ଡାଟା ସେବା ନାହିଁ"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"ଜରୁରୀକାଳୀନ କଲ୍ ଉପଲବ୍ଧ ନାହିଁ"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"କୌଣସି ଭଏସ୍‍ ସେବା ନାହିଁ"</string>
diff --git a/core/res/res/values-pa/strings.xml b/core/res/res/values-pa/strings.xml
index 96917c0..79ab897 100644
--- a/core/res/res/values-pa/strings.xml
+++ b/core/res/res/values-pa/strings.xml
@@ -72,6 +72,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ਪ੍ਰਤਿਬੰਧਿਤ ਨਾ ਕਰਨ ਲਈ ਕਾਲਰ ਆਈ.ਡੀ. ਪੂਰਵ-ਨਿਰਧਾਰਤ। ਅਗਲੀ ਕਾਲ: ਪ੍ਰਤਿਬੰਧਿਤ ਨਹੀਂ"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"ਸੇਵਾ ਪ੍ਰਬੰਧਿਤ ਨਹੀਂ ਹੈ।"</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"ਤੁਸੀਂ ਕਾਲਰ ਆਈ.ਡੀ. ਸੈਟਿੰਗ ਨਹੀਂ ਬਦਲ ਸਕਦੇ।"</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"ਕੋਈ ਮੋਬਾਈਲ ਡਾਟਾ ਸੇਵਾ ਨਹੀਂ"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"ਸੰਕਟਕਾਲੀਨ ਕਾਲਿੰਗ ਉਪਲਬਧ ਨਹੀਂ"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"ਕੋਈ ਆਵਾਜ਼ੀ ਸੇਵਾ ਨਹੀਂ"</string>
diff --git a/core/res/res/values-pl/strings.xml b/core/res/res/values-pl/strings.xml
index be9d322..5e3a64a 100644
--- a/core/res/res/values-pl/strings.xml
+++ b/core/res/res/values-pl/strings.xml
@@ -74,6 +74,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ID rozmówcy ustawiony jest domyślnie na „nie zastrzeżony”. Następne połączenie: nie zastrzeżony"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Usługa nie jest świadczona."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Nie możesz zmienić ustawienia ID rozmówcy."</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Brak komórkowej usługi transmisji danych"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Połączenia alarmowe są niedostępne"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Brak usługi połączeń głosowych"</string>
diff --git a/core/res/res/values-pt-rBR/strings.xml b/core/res/res/values-pt-rBR/strings.xml
index ff352b1..34c0f0c 100644
--- a/core/res/res/values-pt-rBR/strings.xml
+++ b/core/res/res/values-pt-rBR/strings.xml
@@ -73,6 +73,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"O identificador de chamadas assume o padrão de não restrito. Próxima chamada: Não restrita"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"O serviço não foi habilitado."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Não é possível alterar a configuração do identificador de chamadas."</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Nenhum serviço móvel de dados"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Chamadas de emergência indisponíveis"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Sem serviço de voz"</string>
diff --git a/core/res/res/values-pt-rPT/strings.xml b/core/res/res/values-pt-rPT/strings.xml
index d343af6..0fb835b 100644
--- a/core/res/res/values-pt-rPT/strings.xml
+++ b/core/res/res/values-pt-rPT/strings.xml
@@ -73,6 +73,8 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ID do autor da chamada é predefinido com não restrito. Chamada seguinte: Não restrita"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Serviço não fornecido."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Não pode alterar a definição da identificação de chamadas."</string>
+    <string name="auto_data_switch_title" msgid="3286350716870518297">"Os dados móveis foram alterados para o operador <xliff:g id="CARRIERDISPLAY">%s</xliff:g>"</string>
+    <string name="auto_data_switch_content" msgid="803557715007110959">"Pode alterar isto em qualquer altura nas Definições"</string>
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Sem serviço de dados móveis"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Chamadas de emergência indisponíveis"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Sem serviço de voz"</string>
diff --git a/core/res/res/values-pt/strings.xml b/core/res/res/values-pt/strings.xml
index ff352b1..34c0f0c 100644
--- a/core/res/res/values-pt/strings.xml
+++ b/core/res/res/values-pt/strings.xml
@@ -73,6 +73,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"O identificador de chamadas assume o padrão de não restrito. Próxima chamada: Não restrita"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"O serviço não foi habilitado."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Não é possível alterar a configuração do identificador de chamadas."</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Nenhum serviço móvel de dados"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Chamadas de emergência indisponíveis"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Sem serviço de voz"</string>
diff --git a/core/res/res/values-ro/strings.xml b/core/res/res/values-ro/strings.xml
index b560b07..88aab00 100644
--- a/core/res/res/values-ro/strings.xml
+++ b/core/res/res/values-ro/strings.xml
@@ -73,6 +73,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ID-ul apelantului este nerestricționat în mod prestabilit. Apelul următor: nerestricționat"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Nu se asigură accesul la acest serviciu."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Nu poți modifica setarea pentru ID-ul apelantului."</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Fără serviciu de date mobile"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Apelurile de urgență nu sunt disponibile"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Fără servicii vocale"</string>
diff --git a/core/res/res/values-ru/strings.xml b/core/res/res/values-ru/strings.xml
index fbe67e2..33bfe77 100644
--- a/core/res/res/values-ru/strings.xml
+++ b/core/res/res/values-ru/strings.xml
@@ -74,6 +74,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Идентификация абонента по умолчанию не запрещена. След. вызов: разрешена"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Услуга не предоставляется."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Невозможно изменить параметр идентификатора вызывающего абонента."</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Мобильный Интернет недоступен"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Экстренные вызовы недоступны"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Голосовые вызовы недоступны"</string>
diff --git a/core/res/res/values-si/strings.xml b/core/res/res/values-si/strings.xml
index 4cec877..dd5b817 100644
--- a/core/res/res/values-si/strings.xml
+++ b/core/res/res/values-si/strings.xml
@@ -72,6 +72,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"අමතන්නාගේ ID සුපුරුදු අනුව සීමා වී නැත. මීළඟ ඇමතුම: සීමා කර ඇත"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"සේවාවන් සපයා නැත."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"අමතන්නාගේ ID සැකසීම ඔබට වෙනස්කල නොහැක."</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"ජංගම දත්ත සේවාව නැත"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"හදිසි ඇමතුම් ලබා ගත නොහැකිය"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"හඬ සේවාව නැත"</string>
diff --git a/core/res/res/values-sk/strings.xml b/core/res/res/values-sk/strings.xml
index b98364a..40abf79 100644
--- a/core/res/res/values-sk/strings.xml
+++ b/core/res/res/values-sk/strings.xml
@@ -74,6 +74,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"V predvolenom nastavení nie je identifikácia volajúceho obmedzená. Ďalší hovor: Bez obmedzenia"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Služba nie je poskytovaná."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Nemôžete meniť nastavenie identifikácie volajúcich."</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Žiadna mobilná dátová služba"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Tiesňové volania nie sú k dispozícii"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Žiadne hlasové hovory"</string>
diff --git a/core/res/res/values-sl/strings.xml b/core/res/res/values-sl/strings.xml
index 6972abb..53a0321 100644
--- a/core/res/res/values-sl/strings.xml
+++ b/core/res/res/values-sl/strings.xml
@@ -74,6 +74,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ID klicatelja je ponastavljen na neomejeno. Naslednji klic: ni omejeno"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Storitev ni nastavljena in omogočena."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Ne morete spremeniti nastavitve ID-ja klicatelja."</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Ni mobilne podatkovne storitve"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Klicanje v sili ni na voljo"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Ni storitve za glasovne klice"</string>
diff --git a/core/res/res/values-sq/strings.xml b/core/res/res/values-sq/strings.xml
index cbac0f0..6c3f145 100644
--- a/core/res/res/values-sq/strings.xml
+++ b/core/res/res/values-sq/strings.xml
@@ -72,6 +72,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ID-ja e telefonuesit kalon me paracaktim në listën e të telefonuesve të pakufizuar. Telefonata e radhës: e pakufizuar!"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Shërbimi nuk është përgatitur."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Nuk mund ta ndryshosh cilësimin e ID-së së telefonuesit."</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Nuk ka shërbim të të dhënave celulare"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Telefonatat e urgjencës nuk ofrohen"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Nuk ka shërbim zanor"</string>
diff --git a/core/res/res/values-sr/strings.xml b/core/res/res/values-sr/strings.xml
index d5549e7..d23b056 100644
--- a/core/res/res/values-sr/strings.xml
+++ b/core/res/res/values-sr/strings.xml
@@ -73,6 +73,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"ИД позиваоца подразумевано није ограничен. Следећи позив: Није ограничен."</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Услуга није добављена."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Не можете да промените подешавање ИД-а корисника."</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Нема услуге мобилних података"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Хитни позиви нису доступни"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Нема гласовне услуге"</string>
diff --git a/core/res/res/values-sv/strings.xml b/core/res/res/values-sv/strings.xml
index d1c579d..ee6a8ac 100644
--- a/core/res/res/values-sv/strings.xml
+++ b/core/res/res/values-sv/strings.xml
@@ -72,6 +72,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Nummerpresentatörens standardinställning är inte blockerad. Nästa samtal: Inte blockerad"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Tjänsten är inte etablerad."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Det går inte att ändra inställningen för nummerpresentatör."</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Ingen mobildatatjänst"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Det går inte att ringa nödsamtal"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Tjänsten för röstsamtal har blockerats"</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 2bc3f57..7b9d89f 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -72,6 +72,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Chaguomsingi za ID ya mpigaji simu za kutozuia. Simu ifuatayo: Haijazuiliwa"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Huduma haitathminiwi."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Hauwezi kubadilisha mpangilio wa kitambulisho cha anayepiga."</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Hakuna huduma ya data kwa vifaa vya mkononi"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Huduma ya kupiga simu za dharura haipatikani"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Hakuna huduma za simu za sauti"</string>
diff --git a/core/res/res/values-ta/strings.xml b/core/res/res/values-ta/strings.xml
index 9e48a47..2a0fc2c 100644
--- a/core/res/res/values-ta/strings.xml
+++ b/core/res/res/values-ta/strings.xml
@@ -72,6 +72,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"அழைப்பாளர் ஐடி ஆனது வரையறுக்கப்படவில்லை என்பதற்கு இயல்பாக அமைக்கப்பட்டது. அடுத்த அழைப்பு: வரையறுக்கப்படவில்லை"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"சேவை ஒதுக்கப்படவில்லை."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"அழைப்பாளர் ஐடி அமைப்பை மாற்ற முடியாது."</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"மொபைல் டேட்டா சேவையைப் பயன்படுத்த முடியாது"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"அவசர அழைப்பைச் செய்ய முடியாது"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"குரல் சேவை இல்லை"</string>
diff --git a/core/res/res/values-te/strings.xml b/core/res/res/values-te/strings.xml
index eff08bc..a7c2d4e 100644
--- a/core/res/res/values-te/strings.xml
+++ b/core/res/res/values-te/strings.xml
@@ -72,6 +72,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"కాలర్ ID ఆటోమేటిక్‌లపై పరిమితి లేదు. తర్వాత కాల్: పరిమితి లేదు"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"సేవ కేటాయించబడలేదు."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"మీరు కాలర్ ID సెట్టింగ్‌ను మార్చలేరు."</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"మొబైల్ డేటా సేవ లేదు"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"అత్యవసర కాలింగ్ అందుబాటులో లేదు"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"వాయిస్ సర్వీస్ లేదు"</string>
@@ -2071,7 +2075,7 @@
     <string name="notification_appops_camera_active" msgid="8177643089272352083">"కెమెరా"</string>
     <string name="notification_appops_microphone_active" msgid="581333393214739332">"మైక్రోఫోన్"</string>
     <string name="notification_appops_overlay_active" msgid="5571732753262836481">"మీ స్క్రీన్‌పై ఇతర యాప్‌ల ద్వారా ప్రదర్శించబడుతోంది"</string>
-    <string name="notification_feedback_indicator" msgid="663476517711323016">"ఫీడ్‌బ్యాక్‌ను అందించండి"</string>
+    <string name="notification_feedback_indicator" msgid="663476517711323016">"ఫీడ్‌బ్యాక్ ఇవ్వండి"</string>
     <string name="notification_feedback_indicator_alerted" msgid="6552871804121942099">"ఈ నోటిఫికేషన్, ఆటోమేటిక్ సెట్టింగ్‌కు ప్రమోట్ చేయబడింది. ఫీడ్‌బ్యాక్‌ను అందించడానికి ట్యాప్ చేయండి."</string>
     <string name="notification_feedback_indicator_silenced" msgid="3799442124723177262">"ఈ నోటిఫికేషన్ స్థాయి నిశ్శబ్దంగా ఉండేలా తగ్గించబడింది. ఫీడ్‌బ్యాక్‌ను అందించడానికి ట్యాప్ చేయండి."</string>
     <string name="notification_feedback_indicator_promoted" msgid="9030204303764698640">"ఈ నోటిఫికేషన్‌కు ఎక్కువ ర్యాంక్ ఇవ్వబడింది. ఫీడ్‌బ్యాక్‌ను అందించడానికి ట్యాప్ చేయండి."</string>
diff --git a/core/res/res/values-television/config.xml b/core/res/res/values-television/config.xml
index 88bf18c..eaadc20 100644
--- a/core/res/res/values-television/config.xml
+++ b/core/res/res/values-television/config.xml
@@ -54,6 +54,12 @@
         com.android.systemui/com.android.systemui.sensorprivacy.television.TvUnblockSensorActivity
     </string>
 
+    <!-- Component name of the activity used to inform a user about a sensor privacy update from
+     SW/HW privacy switches. -->
+    <string name="config_sensorStateChangedActivity" translatable="false">
+        com.android.systemui/com.android.systemui.sensorprivacy.television.TvSensorPrivacyChangedActivity
+    </string>
+
     <!-- Component name of the activity that shows the request for access to a usb device. -->
     <string name="config_usbPermissionActivity" translatable="false">
         com.android.systemui/com.android.systemui.usb.tv.TvUsbPermissionActivity
diff --git a/core/res/res/values-th/strings.xml b/core/res/res/values-th/strings.xml
index 72cbc36..c0ab275 100644
--- a/core/res/res/values-th/strings.xml
+++ b/core/res/res/values-th/strings.xml
@@ -72,6 +72,8 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"หมายเลขผู้โทรได้รับการตั้งค่าเริ่มต้นเป็นไม่จำกัด การโทรครั้งต่อไป: ไม่จำกัด"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"ไม่มีการนำเสนอบริการ"</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"คุณไม่สามารถเปลี่ยนการตั้งค่าหมายเลขผู้โทร"</string>
+    <string name="auto_data_switch_title" msgid="3286350716870518297">"เปลี่ยนไปใช้อินเทอร์เน็ตมือถือของ <xliff:g id="CARRIERDISPLAY">%s</xliff:g> แล้ว"</string>
+    <string name="auto_data_switch_content" msgid="803557715007110959">"คุณเปลี่ยนตัวเลือกนี้ได้ทุกเมื่อในการตั้งค่า"</string>
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"ไม่มีบริการเน็ตมือถือ"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"หมายเลขฉุกเฉินไม่พร้อมใช้งาน"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"ไม่มีบริการเสียง"</string>
diff --git a/core/res/res/values-tl/strings.xml b/core/res/res/values-tl/strings.xml
index e66999d..7b9807a 100644
--- a/core/res/res/values-tl/strings.xml
+++ b/core/res/res/values-tl/strings.xml
@@ -72,6 +72,8 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Naka-default na hindi pinaghihigpitan ang Caller ID. Susunod na tawag: Hindi pinaghihigpitan"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Hindi naprobisyon ang serbisyo."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Hindi mo mababago ang setting ng caller ID."</string>
+    <string name="auto_data_switch_title" msgid="3286350716870518297">"Nailipat sa <xliff:g id="CARRIERDISPLAY">%s</xliff:g> ang data"</string>
+    <string name="auto_data_switch_content" msgid="803557715007110959">"Puwede mo itong baguhin anumang oras sa Mga Setting"</string>
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Walang serbisyo ng data sa mobile"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Hindi available ang pang-emergency na pagtawag"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Walang serbisyo para sa boses"</string>
diff --git a/core/res/res/values-tr/strings.xml b/core/res/res/values-tr/strings.xml
index b6c4b4a..3459cb0 100644
--- a/core/res/res/values-tr/strings.xml
+++ b/core/res/res/values-tr/strings.xml
@@ -72,6 +72,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Arayan kimliği varsayılanları kısıtlanmamıştır. Sonraki çağrı: Kısıtlanmamış"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Hizmet sağlanamadı."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Arayanın kimliği ayarını değiştiremezsiniz."</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Mobil veri hizmeti yok"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Acil durum çağrısı kullanılamaz"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Sesli çağrı hizmeti yok"</string>
diff --git a/core/res/res/values-uk/strings.xml b/core/res/res/values-uk/strings.xml
index c408c51..2483a30 100644
--- a/core/res/res/values-uk/strings.xml
+++ b/core/res/res/values-uk/strings.xml
@@ -74,6 +74,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Ідентиф. абонента за умовч. не обмеж. Наст. дзвінок: не обмежений"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Службу не ініціалізовано."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Ви не можете змінювати налаштування ідентифікатора абонента."</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Службу передавання мобільних даних заблоковано"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Екстрені виклики недоступні"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Немає голосової служби"</string>
diff --git a/core/res/res/values-ur/strings.xml b/core/res/res/values-ur/strings.xml
index 7784431..7de793c 100644
--- a/core/res/res/values-ur/strings.xml
+++ b/core/res/res/values-ur/strings.xml
@@ -72,6 +72,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"‏کالر ID کی ڈیفالٹ ترتیب غیر محدود کردہ ہے۔ اگلی کال: غیر محدود کردہ"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"سروس فراہم نہیں کی گئی۔"</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"‏آپ کالر ID کی ترتیبات تبدیل نہیں کر سکتے ہیں۔"</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"کوئی موبائل ڈیٹا سروس دستیاب نہیں ہے"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"ہنگامی کالنگ دستیاب نہیں ہے"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"کوئی صوتی سروس نہیں"</string>
diff --git a/core/res/res/values-uz/strings.xml b/core/res/res/values-uz/strings.xml
index 5ac689d1..8c88585 100644
--- a/core/res/res/values-uz/strings.xml
+++ b/core/res/res/values-uz/strings.xml
@@ -72,6 +72,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Qo‘ng‘iroq qiluvchi ma’lumotlari cheklanmagan. Keyingi qo‘ng‘iroq: cheklanmagan"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Xizmat ishalamaydi."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Qo‘ng‘iroq qiluvchining ID raqami sozlamasini o‘zgartirib bo‘lmaydi."</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Mobil internet ishlamayapti"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Favqulodda chaqiruv ishlamayapti"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Ovozli chaqiruvlar ishlamaydi"</string>
@@ -1592,7 +1596,7 @@
     <string name="issued_by" msgid="7872459822431585684">"Tegishli:"</string>
     <string name="validity_period" msgid="1717724283033175968">"Yaroqliligi:"</string>
     <string name="issued_on" msgid="5855489688152497307">"Chiqarilgan sanasi:"</string>
-    <string name="expires_on" msgid="1623640879705103121">"Amal qilish muddati:"</string>
+    <string name="expires_on" msgid="1623640879705103121">"Muddati:"</string>
     <string name="serial_number" msgid="3479576915806623429">"Serial raqam:"</string>
     <string name="fingerprints" msgid="148690767172613723">"Barmoq izlari:"</string>
     <string name="sha256_fingerprint" msgid="7103976380961964600">"SHA-256 barmoq izi:"</string>
diff --git a/core/res/res/values-vi/strings.xml b/core/res/res/values-vi/strings.xml
index e1b479c..68bb062 100644
--- a/core/res/res/values-vi/strings.xml
+++ b/core/res/res/values-vi/strings.xml
@@ -72,6 +72,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"Số gọi đến mặc định thành không bị giới hạn. Cuộc gọi tiếp theo. Không bị giới hạn"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Dịch vụ không được cấp phép."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Bạn không thể thay đổi cài đặt ID người gọi."</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Không có dịch vụ dữ liệu di động"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Không có dịch vụ gọi khẩn cấp"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Không có dịch vụ thoại"</string>
diff --git a/core/res/res/values-zh-rCN/strings.xml b/core/res/res/values-zh-rCN/strings.xml
index 5fce25e..f2b033e 100644
--- a/core/res/res/values-zh-rCN/strings.xml
+++ b/core/res/res/values-zh-rCN/strings.xml
@@ -72,6 +72,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"默认显示本机号码,在下一次通话中也显示"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"未提供服务。"</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"您无法更改来电显示设置。"</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"无法使用移动数据服务"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"无法使用紧急呼救服务"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"无法使用语音通话服务"</string>
diff --git a/core/res/res/values-zh-rHK/strings.xml b/core/res/res/values-zh-rHK/strings.xml
index 61a3f20..63995bb 100644
--- a/core/res/res/values-zh-rHK/strings.xml
+++ b/core/res/res/values-zh-rHK/strings.xml
@@ -72,6 +72,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"預設顯示來電號碼,下一通電話也繼續顯示。"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"未提供此服務。"</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"您無法更改來電顯示設定。"</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"無法使用流動數據服務"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"無法撥打緊急電話"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"沒有語音服務"</string>
diff --git a/core/res/res/values-zh-rTW/strings.xml b/core/res/res/values-zh-rTW/strings.xml
index 10dc699..55f9321 100644
--- a/core/res/res/values-zh-rTW/strings.xml
+++ b/core/res/res/values-zh-rTW/strings.xml
@@ -72,6 +72,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"預設顯示本機號碼,下一通電話也繼續顯示。"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"無法提供此服務。"</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"你無法變更來電顯示設定。"</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"沒有行動數據傳輸服務"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"無法撥打緊急電話"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"無法使用語音通話服務"</string>
diff --git a/core/res/res/values-zu/strings.xml b/core/res/res/values-zu/strings.xml
index 66d639e..2218b76 100644
--- a/core/res/res/values-zu/strings.xml
+++ b/core/res/res/values-zu/strings.xml
@@ -72,6 +72,10 @@
     <string name="CLIRDefaultOffNextCallOff" msgid="2491576172356463443">"I-ID Yomshayeli ishintshela kokungavinjelwe. Ucingo olulandelayo: Aluvinjelwe"</string>
     <string name="serviceNotProvisioned" msgid="8289333510236766193">"Isevisi ayilungiselelwe."</string>
     <string name="CLIRPermanent" msgid="166443681876381118">"Ngeke ukwazi ukuguqul izilungiselelo zemininingwane yoshayayo."</string>
+    <!-- no translation found for auto_data_switch_title (3286350716870518297) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_content (803557715007110959) -->
+    <skip />
     <string name="RestrictedOnDataTitle" msgid="1500576417268169774">"Ayikho isevisi yedatha yeselula"</string>
     <string name="RestrictedOnEmergencyTitle" msgid="2852916906106191866">"Ukushaya okuphuthumayo akutholakali"</string>
     <string name="RestrictedOnNormalTitle" msgid="7009474589746551737">"Ayikho isevisi yezwi"</string>
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 23dd1b4..3d245e9 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2027,10 +2027,6 @@
          STREAM_MUSIC as if it's on TV platform. -->
     <bool name="config_single_volume">false</bool>
 
-    <!-- Flag indicating whether notification and ringtone volumes
-         are controlled together (aliasing is true) or not. -->
-    <bool name="config_alias_ring_notif_stream_types">true</bool>
-
     <!-- The number of volume steps for the notification stream -->
     <integer name="config_audio_notif_vol_steps">7</integer>
 
@@ -2456,8 +2452,10 @@
          assistant activities (ACTIVITY_TYPE_ASSISTANT) -->
     <bool name="config_dismissDreamOnActivityStart">false</bool>
 
-    <!-- The prefix of dream component names that are loggable. If empty, logs "other" for all. -->
-    <string name="config_loggable_dream_prefix" translatable="false"></string>
+    <!-- The prefixes of dream component names that are loggable.
+         Matched against ComponentName#flattenToString() for dream components.
+         If empty, logs "other" for all. -->
+    <string-array name="config_loggable_dream_prefixes"></string-array>
 
     <!-- ComponentName of a dream to show whenever the system would otherwise have
          gone to sleep.  When the PowerManager is asked to go to sleep, it will instead
@@ -2942,6 +2940,9 @@
     <string name="config_usbResolverActivity" translatable="false"
             >com.android.systemui/com.android.systemui.usb.UsbResolverActivity</string>
 
+    <!-- Component name of the activity used to inform a user about a sensor privacy state chage. -->
+    <string name="config_sensorStateChangedActivity" translatable="false"></string>
+
     <!-- Component name of the activity used to inform a user about a sensory being blocked because
      of privacy settings. -->
     <string name="config_sensorUseStartedActivity" translatable="false"
@@ -4425,13 +4426,13 @@
     <string name="config_mediaProjectionPermissionDialogComponent" translatable="false">com.android.systemui/com.android.systemui.media.MediaProjectionPermissionActivity</string>
 
     <!-- Corner radius of system dialogs -->
-    <dimen name="config_dialogCornerRadius">2dp</dimen>
+    <dimen name="config_dialogCornerRadius">28dp</dimen>
     <!-- Corner radius of system buttons -->
-    <dimen name="config_buttonCornerRadius">@dimen/control_corner_material</dimen>
+    <dimen name="config_buttonCornerRadius">4dp</dimen>
     <!-- Corner radius for bottom sheet system dialogs -->
-    <dimen name="config_bottomDialogCornerRadius">@dimen/config_dialogCornerRadius</dimen>
+    <dimen name="config_bottomDialogCornerRadius">16dp</dimen>
     <!-- Corner radius of system progress bars -->
-    <dimen name="config_progressBarCornerRadius">@dimen/progress_bar_corner_material</dimen>
+    <dimen name="config_progressBarCornerRadius">1000dp</dimen>
     <!-- Controls whether system buttons use all caps for text -->
     <bool name="config_buttonTextAllCaps">true</bool>
     <!-- Name of the font family used for system surfaces where the font should use medium weight -->
@@ -4945,6 +4946,11 @@
     <!-- If face auth sends the user directly to home/last open app, or stays on keyguard -->
     <bool name="config_faceAuthDismissesKeyguard">true</bool>
 
+    <!-- Default value for whether a SFPS device is required to be
+        {@link KeyguardUpdateMonitor#isDeviceInteractive()} for fingerprint auth
+        to unlock the device. -->
+    <bool name="config_requireScreenOnToAuthEnabled">false</bool>
+
     <!-- The component name for the default profile supervisor, which can be set as a profile owner
     even after user setup is complete. The defined component should be used for supervision purposes
     only. The component must be part of a system app. -->
diff --git a/core/res/res/values/dimens.xml b/core/res/res/values/dimens.xml
index 1997261..9dbb6a0 100644
--- a/core/res/res/values/dimens.xml
+++ b/core/res/res/values/dimens.xml
@@ -979,9 +979,9 @@
     <dimen name="controls_thumbnail_image_max_width">280dp</dimen>
 
     <!-- System-provided radius for the background view of app widgets. The resolved value of this resource may change at runtime. -->
-    <dimen name="system_app_widget_background_radius">16dp</dimen>
+    <dimen name="system_app_widget_background_radius">28dp</dimen>
     <!-- System-provided radius for inner views on app widgets. The resolved value of this resource may change at runtime. -->
-    <dimen name="system_app_widget_inner_radius">8dp</dimen>
+    <dimen name="system_app_widget_inner_radius">20dp</dimen>
     <!-- System-provided padding for inner views on app widgets. The resolved value of this resource may change at runtime. @removed -->
     <dimen name="__removed_system_app_widget_internal_padding">16dp</dimen>
 
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 509de33..1f459c6 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1933,6 +1933,11 @@
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] -->
     <string name="permdesc_readMediaImages">Allows the app to read image files from your shared storage.</string>
 
+    <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] -->
+    <string name="permlab_readVisualUserSelect">read user selected image and video files from shared storage</string>
+    <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can read from. [CHAR LIMIT=none] -->
+    <string name="permdesc_readVisualUserSelect">Allows the app to read image and video files that you select from your shared storage.</string>
+
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can write to. [CHAR LIMIT=none] -->
     <string name="permlab_sdcardWrite">modify or delete the contents of your shared storage</string>
     <!-- Description of an application permission, listed so the user can choose whether they want to allow the application to do this. "shared storage" refers to a storage space on the device that all apps with this permission can write to. [CHAR LIMIT=none] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 476d36d..f2bbbc4 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -282,7 +282,6 @@
   <java-symbol type="attr" name="autofillSaveCustomSubtitleMaxHeight"/>
   <java-symbol type="bool" name="action_bar_embed_tabs" />
   <java-symbol type="bool" name="action_bar_expanded_action_views_exclusive" />
-  <java-symbol type="bool" name="config_alias_ring_notif_stream_types" />
   <java-symbol type="integer" name="config_audio_notif_vol_default" />
   <java-symbol type="integer" name="config_audio_notif_vol_steps" />
   <java-symbol type="integer" name="config_audio_ring_vol_default" />
@@ -374,6 +373,7 @@
   <java-symbol type="string" name="config_usbResolverActivity" />
   <java-symbol type="string" name="config_sensorUseStartedActivity" />
   <java-symbol type="string" name="config_sensorUseStartedActivity_hwToggle" />
+  <java-symbol type="string" name="config_sensorStateChangedActivity" />
   <java-symbol type="string" name="config_hdmiCecSetMenuLanguageActivity" />
   <java-symbol type="integer" name="config_minNumVisibleRecentTasks_lowRam" />
   <java-symbol type="integer" name="config_maxNumVisibleRecentTasks_lowRam" />
@@ -2246,7 +2246,7 @@
   <java-symbol type="integer" name="config_dreamOverlayReconnectTimeoutMs" />
   <java-symbol type="integer" name="config_dreamOverlayMaxReconnectAttempts" />
   <java-symbol type="integer" name="config_minDreamOverlayDurationMs" />
-  <java-symbol type="string" name="config_loggable_dream_prefix" />
+  <java-symbol type="array" name="config_loggable_dream_prefixes" />
   <java-symbol type="string" name="config_dozeComponent" />
   <java-symbol type="string" name="enable_explore_by_touch_warning_title" />
   <java-symbol type="string" name="enable_explore_by_touch_warning_message" />
@@ -2725,6 +2725,7 @@
   <java-symbol type="array" name="config_face_acquire_vendor_biometricprompt_ignorelist" />
   <java-symbol type="bool" name="config_faceAuthSupportsSelfIllumination" />
   <java-symbol type="bool" name="config_faceAuthDismissesKeyguard" />
+  <java-symbol type="bool" name="config_requireScreenOnToAuthEnabled" />
 
   <!-- Face config -->
   <java-symbol type="integer" name="config_faceMaxTemplatesPerUser" />
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramSelectorTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramSelectorTest.java
index 57b9cb1..5bd018b 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramSelectorTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/ProgramSelectorTest.java
@@ -23,11 +23,13 @@
 import android.annotation.Nullable;
 import android.hardware.radio.ProgramSelector;
 import android.hardware.radio.RadioManager;
+import android.os.Parcel;
 
 import org.junit.Test;
 
 public final class ProgramSelectorTest {
 
+    private static final int CREATOR_ARRAY_SIZE = 2;
     private static final int FM_PROGRAM_TYPE = ProgramSelector.PROGRAM_TYPE_FM;
     private static final int DAB_PROGRAM_TYPE = ProgramSelector.PROGRAM_TYPE_DAB;
     private static final long FM_FREQUENCY = 88500;
@@ -97,6 +99,33 @@
     }
 
     @Test
+    public void describeContents_forIdentifier() {
+        assertWithMessage("FM identifier contents")
+                .that(FM_IDENTIFIER.describeContents()).isEqualTo(0);
+    }
+
+    @Test
+    public void newArray_forIdentifierCreator() {
+        ProgramSelector.Identifier[] identifiers =
+                ProgramSelector.Identifier.CREATOR.newArray(CREATOR_ARRAY_SIZE);
+
+        assertWithMessage("Identifiers").that(identifiers).hasLength(CREATOR_ARRAY_SIZE);
+    }
+
+    @Test
+    public void writeToParcel_forIdentifier() {
+        Parcel parcel = Parcel.obtain();
+
+        FM_IDENTIFIER.writeToParcel(parcel, /* flags= */ 0);
+        parcel.setDataPosition(0);
+
+        ProgramSelector.Identifier identifierFromParcel =
+                ProgramSelector.Identifier.CREATOR.createFromParcel(parcel);
+        assertWithMessage("Identifier created from parcel")
+                .that(identifierFromParcel).isEqualTo(FM_IDENTIFIER);
+    }
+
+    @Test
     public void getProgramType() {
         ProgramSelector selector = getFmSelector(/* secondaryIds= */ null, /* vendorIds= */ null);
 
@@ -394,6 +423,34 @@
                 .that(selector1.strictEquals(selector2)).isTrue();
     }
 
+    @Test
+    public void describeContents_forProgramSelector() {
+        assertWithMessage("FM selector contents")
+                .that(getFmSelector(/* secondaryIds= */ null, /* vendorIds= */ null)
+                        .describeContents()).isEqualTo(0);
+    }
+
+    @Test
+    public void newArray_forProgramSelectorCreator() {
+        ProgramSelector[] programSelectors = ProgramSelector.CREATOR.newArray(CREATOR_ARRAY_SIZE);
+
+        assertWithMessage("Program selectors").that(programSelectors).hasLength(CREATOR_ARRAY_SIZE);
+    }
+
+    @Test
+    public void writeToParcel_forProgramSelector() {
+        ProgramSelector selectorExpected =
+                getFmSelector(/* secondaryIds= */ null, /* vendorIds= */ null);
+        Parcel parcel = Parcel.obtain();
+
+        selectorExpected.writeToParcel(parcel, /* flags= */ 0);
+        parcel.setDataPosition(0);
+
+        ProgramSelector selectorFromParcel = ProgramSelector.CREATOR.createFromParcel(parcel);
+        assertWithMessage("Program selector created from parcel")
+                .that(selectorFromParcel).isEqualTo(selectorExpected);
+    }
+
     private ProgramSelector getFmSelector(@Nullable ProgramSelector.Identifier[] secondaryIds,
             @Nullable long[] vendorIds) {
         return new ProgramSelector(FM_PROGRAM_TYPE, FM_IDENTIFIER, secondaryIds, vendorIds);
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioAnnouncementTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioAnnouncementTest.java
index 42143b9..6e1bb4b4 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioAnnouncementTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioAnnouncementTest.java
@@ -22,6 +22,7 @@
 
 import android.hardware.radio.Announcement;
 import android.hardware.radio.ProgramSelector;
+import android.os.Parcel;
 import android.util.ArrayMap;
 
 import org.junit.Test;
@@ -83,4 +84,35 @@
         vendorInfo.put("vendorKeyMock", "vendorValueMock");
         return vendorInfo;
     }
+
+    @Test
+    public void describeContents_forAnnouncement() {
+        assertWithMessage("Radio announcement contents")
+                .that(TEST_ANNOUNCEMENT.describeContents()).isEqualTo(0);
+    }
+
+    @Test
+    public void newArray_forAnnouncementCreator() {
+        int sizeExpected = 2;
+
+        Announcement[] announcements = Announcement.CREATOR.newArray(sizeExpected);
+
+        assertWithMessage("Announcements").that(announcements).hasLength(sizeExpected);
+    }
+
+    @Test
+    public void writeToParcel_forAnnouncement() {
+        Parcel parcel = Parcel.obtain();
+
+        TEST_ANNOUNCEMENT.writeToParcel(parcel, /* flags= */ 0);
+        parcel.setDataPosition(0);
+
+        Announcement announcementFromParcel = Announcement.CREATOR.createFromParcel(parcel);
+        assertWithMessage("Selector of announcement created from parcel")
+                .that(announcementFromParcel.getSelector()).isEqualTo(FM_PROGRAM_SELECTOR);
+        assertWithMessage("Type of announcement created from parcel")
+                .that(announcementFromParcel.getType()).isEqualTo(TRAFFIC_ANNOUNCEMENT_TYPE);
+        assertWithMessage("Vendor info of announcement created from parcel")
+                .that(announcementFromParcel.getVendorInfo()).isEqualTo(VENDOR_INFO);
+    }
 }
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java
index be4d0d4..f838a5d 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioManagerTest.java
@@ -33,6 +33,7 @@
 import android.hardware.radio.RadioManager;
 import android.hardware.radio.RadioMetadata;
 import android.hardware.radio.RadioTuner;
+import android.os.Parcel;
 import android.os.RemoteException;
 import android.util.ArrayMap;
 
@@ -80,6 +81,8 @@
     private static final int[] SUPPORTED_IDENTIFIERS_TYPES = new int[]{
             ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, ProgramSelector.IDENTIFIER_TYPE_RDS_PI};
 
+    private static final int CREATOR_ARRAY_SIZE = 3;
+
     private static final RadioManager.FmBandDescriptor FM_BAND_DESCRIPTOR =
             createFmBandDescriptor();
     private static final RadioManager.AmBandDescriptor AM_BAND_DESCRIPTOR =
@@ -173,6 +176,22 @@
     }
 
     @Test
+    public void describeContents_forBandDescriptor() {
+        RadioManager.BandDescriptor bandDescriptor = createFmBandDescriptor();
+
+        assertWithMessage("Band Descriptor contents")
+                .that(bandDescriptor.describeContents()).isEqualTo(0);
+    }
+
+    @Test
+    public void newArray_forBandDescriptorCreator() {
+        RadioManager.BandDescriptor[] bandDescriptors =
+                RadioManager.BandDescriptor.CREATOR.newArray(CREATOR_ARRAY_SIZE);
+
+        assertWithMessage("Band Descriptors").that(bandDescriptors).hasLength(CREATOR_ARRAY_SIZE);
+    }
+
+    @Test
     public void isAmBand_forAmBandDescriptor_returnsTrue() {
         RadioManager.BandDescriptor bandDescriptor = createAmBandDescriptor();
 
@@ -219,18 +238,73 @@
     }
 
     @Test
+    public void describeContents_forFmBandDescriptor() {
+        assertWithMessage("FM Band Descriptor contents")
+                .that(FM_BAND_DESCRIPTOR.describeContents()).isEqualTo(0);
+    }
+
+    @Test
+    public void writeToParcel_forFmBandDescriptor() {
+        Parcel parcel = Parcel.obtain();
+
+        FM_BAND_DESCRIPTOR.writeToParcel(parcel, /* flags= */ 0);
+        parcel.setDataPosition(0);
+
+        RadioManager.FmBandDescriptor fmBandDescriptorFromParcel =
+                RadioManager.FmBandDescriptor.CREATOR.createFromParcel(parcel);
+        assertWithMessage("FM Band Descriptor created from parcel")
+                .that(fmBandDescriptorFromParcel).isEqualTo(FM_BAND_DESCRIPTOR);
+    }
+
+    @Test
+    public void newArray_forFmBandDescriptorCreator() {
+        RadioManager.FmBandDescriptor[] fmBandDescriptors =
+                RadioManager.FmBandDescriptor.CREATOR.newArray(CREATOR_ARRAY_SIZE);
+
+        assertWithMessage("FM Band Descriptors")
+                .that(fmBandDescriptors).hasLength(CREATOR_ARRAY_SIZE);
+    }
+
+    @Test
     public void isStereoSupported_forAmBandDescriptor() {
         assertWithMessage("AM Band Descriptor stereo")
                 .that(AM_BAND_DESCRIPTOR.isStereoSupported()).isEqualTo(STEREO_SUPPORTED);
     }
 
     @Test
+    public void describeContents_forAmBandDescriptor() {
+        assertWithMessage("AM Band Descriptor contents")
+                .that(AM_BAND_DESCRIPTOR.describeContents()).isEqualTo(0);
+    }
+
+    @Test
+    public void writeToParcel_forAmBandDescriptor() {
+        Parcel parcel = Parcel.obtain();
+
+        AM_BAND_DESCRIPTOR.writeToParcel(parcel, /* flags= */ 0);
+        parcel.setDataPosition(0);
+
+        RadioManager.AmBandDescriptor amBandDescriptorFromParcel =
+                RadioManager.AmBandDescriptor.CREATOR.createFromParcel(parcel);
+        assertWithMessage("FM Band Descriptor created from parcel")
+                .that(amBandDescriptorFromParcel).isEqualTo(AM_BAND_DESCRIPTOR);
+    }
+
+    @Test
+    public void newArray_forAmBandDescriptorCreator() {
+        RadioManager.AmBandDescriptor[] amBandDescriptors =
+                RadioManager.AmBandDescriptor.CREATOR.newArray(CREATOR_ARRAY_SIZE);
+
+        assertWithMessage("AM Band Descriptors")
+                .that(amBandDescriptors).hasLength(CREATOR_ARRAY_SIZE);
+    }
+
+    @Test
     public void equals_withSameFmBandDescriptors_returnsTrue() {
-        RadioManager.FmBandDescriptor fmBandDescriptor1 = createFmBandDescriptor();
-        RadioManager.FmBandDescriptor fmBandDescriptor2 = createFmBandDescriptor();
+        RadioManager.FmBandDescriptor fmBandDescriptorCompared = createFmBandDescriptor();
 
         assertWithMessage("The same FM Band Descriptor")
-                .that(fmBandDescriptor1).isEqualTo(fmBandDescriptor2);
+                .that(FM_BAND_DESCRIPTOR).isEqualTo(fmBandDescriptorCompared);
     }
 
     @Test
@@ -258,6 +332,44 @@
     }
 
     @Test
+    public void hashCode_withSameFmBandDescriptors_equals() {
+        RadioManager.FmBandDescriptor fmBandDescriptorCompared = createFmBandDescriptor();
+
+        assertWithMessage("Hash code of the same FM Band Descriptor")
+                .that(fmBandDescriptorCompared.hashCode()).isEqualTo(FM_BAND_DESCRIPTOR.hashCode());
+    }
+
+    @Test
+    public void hashCode_withSameAmBandDescriptors_equals() {
+        RadioManager.AmBandDescriptor amBandDescriptorCompared = createAmBandDescriptor();
+
+        assertWithMessage("Hash code of the same AM Band Descriptor")
+                .that(amBandDescriptorCompared.hashCode()).isEqualTo(AM_BAND_DESCRIPTOR.hashCode());
+    }
+
+    @Test
+    public void hashCode_withFmBandDescriptorsOfDifferentAfSupports_notEquals() {
+        RadioManager.FmBandDescriptor fmBandDescriptorCompared = new RadioManager.FmBandDescriptor(
+                REGION, RadioManager.BAND_FM, FM_LOWER_LIMIT, FM_UPPER_LIMIT, FM_SPACING,
+                STEREO_SUPPORTED, RDS_SUPPORTED, TA_SUPPORTED, !AF_SUPPORTED, EA_SUPPORTED);
+
+        assertWithMessage("Hash code of FM Band Descriptor of different spacing")
+                .that(fmBandDescriptorCompared.hashCode())
+                .isNotEqualTo(FM_BAND_DESCRIPTOR.hashCode());
+    }
+
+    @Test
+    public void hashCode_withAmBandDescriptorsOfDifferentSpacings_notEquals() {
+        RadioManager.AmBandDescriptor amBandDescriptorCompared =
+                new RadioManager.AmBandDescriptor(REGION, RadioManager.BAND_AM, AM_LOWER_LIMIT,
+                        AM_UPPER_LIMIT, AM_SPACING * 2, STEREO_SUPPORTED);
+
+        assertWithMessage("Hash code of AM Band Descriptor of different spacing")
+                .that(amBandDescriptorCompared.hashCode())
+                .isNotEqualTo(AM_BAND_DESCRIPTOR.hashCode());
+    }
+
+    @Test
     public void getType_forBandConfig() {
         RadioManager.BandConfig fmBandConfig = createFmBandConfig();
 
@@ -298,8 +410,24 @@
     }
 
     @Test
+    public void describeContents_forBandConfig() {
+        RadioManager.BandConfig bandConfig = createFmBandConfig();
+
+        assertWithMessage("FM Band Config contents")
+                .that(bandConfig.describeContents()).isEqualTo(0);
+    }
+
+    @Test
+    public void newArray_forBandConfigCreator() {
+        RadioManager.BandConfig[] bandConfigs =
+                RadioManager.BandConfig.CREATOR.newArray(CREATOR_ARRAY_SIZE);
+
+        assertWithMessage("Band Configs").that(bandConfigs).hasLength(CREATOR_ARRAY_SIZE);
+    }
+
+    @Test
     public void getStereo_forFmBandConfig() {
-        assertWithMessage("FM Band Config stereo ")
+        assertWithMessage("FM Band Config stereo")
                 .that(FM_BAND_CONFIG.getStereo()).isEqualTo(STEREO_SUPPORTED);
     }
 
@@ -328,12 +456,66 @@
     }
 
     @Test
+    public void describeContents_forFmBandConfig() {
+        assertWithMessage("FM Band Config contents")
+                .that(FM_BAND_CONFIG.describeContents()).isEqualTo(0);
+    }
+
+    @Test
+    public void writeToParcel_forFmBandConfig() {
+        Parcel parcel = Parcel.obtain();
+
+        FM_BAND_CONFIG.writeToParcel(parcel, /* flags= */ 0);
+        parcel.setDataPosition(0);
+
+        RadioManager.FmBandConfig fmBandConfigFromParcel =
+                RadioManager.FmBandConfig.CREATOR.createFromParcel(parcel);
+        assertWithMessage("FM Band Config created from parcel")
+                .that(fmBandConfigFromParcel).isEqualTo(FM_BAND_CONFIG);
+    }
+
+    @Test
+    public void newArray_forFmBandConfigCreator() {
+        RadioManager.FmBandConfig[] fmBandConfigs =
+                RadioManager.FmBandConfig.CREATOR.newArray(CREATOR_ARRAY_SIZE);
+
+        assertWithMessage("FM Band Configs").that(fmBandConfigs).hasLength(CREATOR_ARRAY_SIZE);
+    }
+
+    @Test
     public void getStereo_forAmBandConfig() {
         assertWithMessage("AM Band Config stereo")
                 .that(AM_BAND_CONFIG.getStereo()).isEqualTo(STEREO_SUPPORTED);
     }
 
     @Test
+    public void describeContents_forAmBandConfig() {
+        assertWithMessage("AM Band Config contents")
+                .that(AM_BAND_CONFIG.describeContents()).isEqualTo(0);
+    }
+
+    @Test
+    public void writeToParcel_forAmBandConfig() {
+        Parcel parcel = Parcel.obtain();
+
+        AM_BAND_CONFIG.writeToParcel(parcel, /* flags= */ 0);
+        parcel.setDataPosition(0);
+
+        RadioManager.AmBandConfig amBandConfigFromParcel =
+                RadioManager.AmBandConfig.CREATOR.createFromParcel(parcel);
+        assertWithMessage("AM Band Config created from parcel")
+                .that(amBandConfigFromParcel).isEqualTo(AM_BAND_CONFIG);
+    }
+
+    @Test
+    public void newArray_forAmBandConfigCreator() {
+        RadioManager.AmBandConfig[] amBandConfigs =
+                RadioManager.AmBandConfig.CREATOR.newArray(CREATOR_ARRAY_SIZE);
+
+        assertWithMessage("AM Band Configs").that(amBandConfigs).hasLength(CREATOR_ARRAY_SIZE);
+    }
+
+    @Test
     public void equals_withSameFmBandConfigs_returnsTrue() {
         RadioManager.FmBandConfig fmBandConfigCompared = createFmBandConfig();
 
@@ -387,6 +569,43 @@
     }
 
     @Test
+    public void hashCode_withSameFmBandConfigs_equals() {
+        RadioManager.FmBandConfig fmBandConfigCompared = createFmBandConfig();
+
+        assertWithMessage("Hash code of the same FM Band Config")
+                .that(FM_BAND_CONFIG.hashCode()).isEqualTo(fmBandConfigCompared.hashCode());
+    }
+
+    @Test
+    public void hashCode_withSameAmBandConfigs_equals() {
+        RadioManager.AmBandConfig amBandConfigCompared = createAmBandConfig();
+
+        assertWithMessage("Hash code of the same AM Band Config")
+                .that(amBandConfigCompared.hashCode()).isEqualTo(AM_BAND_CONFIG.hashCode());
+    }
+
+    @Test
+    public void hashCode_withFmBandConfigsOfDifferentTypes_notEquals() {
+        RadioManager.FmBandConfig fmBandConfigCompared = new RadioManager.FmBandConfig(
+                new RadioManager.FmBandDescriptor(REGION, RadioManager.BAND_FM_HD, FM_LOWER_LIMIT,
+                        FM_UPPER_LIMIT, FM_SPACING, STEREO_SUPPORTED, RDS_SUPPORTED, TA_SUPPORTED,
+                        AF_SUPPORTED, EA_SUPPORTED));
+
+        assertWithMessage("Hash code of FM Band Config with different type")
+                .that(fmBandConfigCompared.hashCode()).isNotEqualTo(FM_BAND_CONFIG.hashCode());
+    }
+
+    @Test
+    public void hashCode_withAmBandConfigsOfDifferentStereoSupports_notEquals() {
+        RadioManager.AmBandConfig amBandConfigCompared = new RadioManager.AmBandConfig(
+                new RadioManager.AmBandDescriptor(REGION, RadioManager.BAND_AM, AM_LOWER_LIMIT,
+                        AM_UPPER_LIMIT, AM_SPACING, !STEREO_SUPPORTED));
+
+        assertWithMessage("Hash code of AM Band Config with different stereo support")
+                .that(amBandConfigCompared.hashCode()).isNotEqualTo(AM_BAND_CONFIG.hashCode());
+    }
+
+    @Test
     public void getId_forModuleProperties() {
         assertWithMessage("Properties id")
                 .that(AMFM_PROPERTIES.getId()).isEqualTo(PROPERTIES_ID);
@@ -509,6 +728,12 @@
     }
 
     @Test
+    public void describeContents_forModuleProperties() {
+        assertWithMessage("Module properties contents")
+                .that(AMFM_PROPERTIES.describeContents()).isEqualTo(0);
+    }
+
+    @Test
     public void equals_withSameProperties_returnsTrue() {
         RadioManager.ModuleProperties propertiesCompared = createAmFmProperties();
 
@@ -530,6 +755,23 @@
     }
 
     @Test
+    public void hashCode_withSameModuleProperties_equals() {
+        RadioManager.ModuleProperties propertiesCompared = createAmFmProperties();
+
+        assertWithMessage("Hash code of the same module properties")
+                .that(propertiesCompared.hashCode()).isEqualTo(AMFM_PROPERTIES.hashCode());
+    }
+
+    @Test
+    public void newArray_forModulePropertiesCreator() {
+        RadioManager.ModuleProperties[] modulePropertiesArray =
+                RadioManager.ModuleProperties.CREATOR.newArray(CREATOR_ARRAY_SIZE);
+
+        assertWithMessage("Module properties array")
+                .that(modulePropertiesArray).hasLength(CREATOR_ARRAY_SIZE);
+    }
+
+    @Test
     public void getSelector_forProgramInfo() {
         assertWithMessage("Selector of DAB program info")
                 .that(DAB_PROGRAM_INFO.getSelector()).isEqualTo(DAB_SELECTOR);
@@ -549,7 +791,7 @@
 
     @Test
     public void getRelatedContent_forProgramInfo() {
-        assertWithMessage("Related contents of DAB program info")
+        assertWithMessage("DAB program info contents")
                 .that(DAB_PROGRAM_INFO.getRelatedContent())
                 .containsExactly(DAB_SID_EXT_IDENTIFIER_RELATED);
     }
@@ -627,6 +869,33 @@
     }
 
     @Test
+    public void describeContents_forProgramInfo() {
+        assertWithMessage("Program info contents")
+                .that(DAB_PROGRAM_INFO.describeContents()).isEqualTo(0);
+    }
+
+    @Test
+    public void newArray_forProgramInfoCreator() {
+        RadioManager.ProgramInfo[] programInfoArray =
+                RadioManager.ProgramInfo.CREATOR.newArray(CREATOR_ARRAY_SIZE);
+
+        assertWithMessage("Program infos").that(programInfoArray).hasLength(CREATOR_ARRAY_SIZE);
+    }
+
+    @Test
+    public void writeToParcel_forProgramInfo() {
+        Parcel parcel = Parcel.obtain();
+
+        DAB_PROGRAM_INFO.writeToParcel(parcel, /* flags= */ 0);
+        parcel.setDataPosition(0);
+
+        RadioManager.ProgramInfo programInfoFromParcel =
+                RadioManager.ProgramInfo.CREATOR.createFromParcel(parcel);
+        assertWithMessage("Program info created from parcel")
+                .that(programInfoFromParcel).isEqualTo(DAB_PROGRAM_INFO);
+    }
+
+    @Test
     public void equals_withSameProgramInfo_returnsTrue() {
         RadioManager.ProgramInfo dabProgramInfoCompared = createDabProgramInfo(DAB_SELECTOR);
 
diff --git a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioMetadataTest.java b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioMetadataTest.java
index fe15597..5771135 100644
--- a/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioMetadataTest.java
+++ b/core/tests/BroadcastRadioTests/src/android/hardware/radio/tests/unittests/RadioMetadataTest.java
@@ -20,18 +20,63 @@
 
 import static org.junit.Assert.assertThrows;
 
+import android.graphics.Bitmap;
 import android.hardware.radio.RadioMetadata;
+import android.os.Parcel;
 
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
 
 import java.util.Set;
 
+@RunWith(MockitoJUnitRunner.class)
 public final class RadioMetadataTest {
 
+    private static final int CREATOR_ARRAY_SIZE = 3;
     private static final int INT_KEY_VALUE = 1;
+    private static final long TEST_UTC_SECOND_SINCE_EPOCH = 200;
+    private static final int TEST_TIME_ZONE_OFFSET_MINUTES = 1;
 
     private final RadioMetadata.Builder mBuilder = new RadioMetadata.Builder();
 
+    @Mock
+    private Bitmap mBitmapValue;
+
+    @Test
+    public void describeContents_forClock() {
+        RadioMetadata.Clock clock = new RadioMetadata.Clock(TEST_UTC_SECOND_SINCE_EPOCH,
+                TEST_TIME_ZONE_OFFSET_MINUTES);
+
+        assertWithMessage("Describe contents for metadata clock")
+                .that(clock.describeContents()).isEqualTo(0);
+    }
+
+    @Test
+    public void newArray_forClockCreator() {
+        RadioMetadata.Clock[] clocks = RadioMetadata.Clock.CREATOR.newArray(CREATOR_ARRAY_SIZE);
+
+        assertWithMessage("Clock array size").that(clocks.length).isEqualTo(CREATOR_ARRAY_SIZE);
+    }
+
+    @Test
+    public void writeToParcel_forClock() {
+        RadioMetadata.Clock clockExpected = new RadioMetadata.Clock(TEST_UTC_SECOND_SINCE_EPOCH,
+                TEST_TIME_ZONE_OFFSET_MINUTES);
+        Parcel parcel = Parcel.obtain();
+
+        clockExpected.writeToParcel(parcel, /* flags= */ 0);
+        parcel.setDataPosition(0);
+
+        RadioMetadata.Clock clockFromParcel = RadioMetadata.Clock.CREATOR.createFromParcel(parcel);
+        assertWithMessage("UTC second since epoch of metadata clock created from parcel")
+                .that(clockFromParcel.getUtcEpochSeconds()).isEqualTo(TEST_UTC_SECOND_SINCE_EPOCH);
+        assertWithMessage("Time zone offset minutes of metadata clock created from parcel")
+                .that(clockFromParcel.getTimezoneOffsetMinutes())
+                .isEqualTo(TEST_TIME_ZONE_OFFSET_MINUTES);
+    }
+
     @Test
     public void putString_withIllegalKey() {
         String invalidStringKey = RadioMetadata.METADATA_KEY_RDS_PI;
@@ -129,22 +174,56 @@
     }
 
     @Test
+    public void getBitmap_withKeyInMetadata() {
+        String key = RadioMetadata.METADATA_KEY_ICON;
+        RadioMetadata metadata = mBuilder.putBitmap(key, mBitmapValue).build();
+
+        assertWithMessage("Bitmap value for key %s in metadata", key)
+                .that(metadata.getBitmap(key)).isEqualTo(mBitmapValue);
+    }
+
+    @Test
+    public void getBitmap_withKeyNotInMetadata() {
+        String key = RadioMetadata.METADATA_KEY_ICON;
+        RadioMetadata metadata = mBuilder.build();
+
+        assertWithMessage("Bitmap value for key %s not in metadata", key)
+                .that(metadata.getBitmap(key)).isNull();
+    }
+
+    @Test
+    public void getBitmapId_withKeyInMetadata() {
+        String key = RadioMetadata.METADATA_KEY_ART;
+        RadioMetadata metadata = mBuilder.putInt(key, INT_KEY_VALUE).build();
+
+        assertWithMessage("Bitmap id value for key %s in metadata", key)
+                .that(metadata.getBitmapId(key)).isEqualTo(INT_KEY_VALUE);
+    }
+
+    @Test
+    public void getBitmapId_withKeyNotInMetadata() {
+        String key = RadioMetadata.METADATA_KEY_ART;
+        RadioMetadata metadata = mBuilder.build();
+
+        assertWithMessage("Bitmap id value for key %s not in metadata", key)
+                .that(metadata.getBitmapId(key)).isEqualTo(0);
+    }
+
+    @Test
     public void getClock_withKeyInMetadata() {
         String key = RadioMetadata.METADATA_KEY_CLOCK;
-        long utcSecondsSinceEpochExpected = 200;
-        int timezoneOffsetMinutesExpected = 1;
         RadioMetadata metadata = mBuilder
-                .putClock(key, utcSecondsSinceEpochExpected, timezoneOffsetMinutesExpected)
+                .putClock(key, TEST_UTC_SECOND_SINCE_EPOCH, TEST_TIME_ZONE_OFFSET_MINUTES)
                 .build();
 
         RadioMetadata.Clock clockExpected = metadata.getClock(key);
 
         assertWithMessage("Number of seconds since epoch of value for key %s in metadata", key)
                 .that(clockExpected.getUtcEpochSeconds())
-                .isEqualTo(utcSecondsSinceEpochExpected);
+                .isEqualTo(TEST_UTC_SECOND_SINCE_EPOCH);
         assertWithMessage("Offset of timezone in minutes of value for key %s in metadata", key)
                 .that(clockExpected.getTimezoneOffsetMinutes())
-                .isEqualTo(timezoneOffsetMinutesExpected);
+                .isEqualTo(TEST_TIME_ZONE_OFFSET_MINUTES);
     }
 
     @Test
@@ -180,12 +259,13 @@
         RadioMetadata metadata = mBuilder
                 .putInt(RadioMetadata.METADATA_KEY_RDS_PI, INT_KEY_VALUE)
                 .putString(RadioMetadata.METADATA_KEY_ARTIST, "artistTest")
+                .putBitmap(RadioMetadata.METADATA_KEY_ICON, mBitmapValue)
                 .build();
 
         Set<String> metadataSet = metadata.keySet();
 
         assertWithMessage("Metadata set of non-empty metadata")
-                .that(metadataSet).containsExactly(
+                .that(metadataSet).containsExactly(RadioMetadata.METADATA_KEY_ICON,
                         RadioMetadata.METADATA_KEY_RDS_PI, RadioMetadata.METADATA_KEY_ARTIST);
     }
 
@@ -208,4 +288,46 @@
                 .that(key).isEqualTo(RadioMetadata.METADATA_KEY_RDS_PI);
     }
 
+    @Test
+    public void equals_forMetadataWithSameContents_returnsTrue() {
+        RadioMetadata metadata = mBuilder
+                .putInt(RadioMetadata.METADATA_KEY_RDS_PI, INT_KEY_VALUE)
+                .putString(RadioMetadata.METADATA_KEY_ARTIST, "artistTest")
+                .build();
+        RadioMetadata.Builder copyBuilder = new RadioMetadata.Builder(metadata);
+        RadioMetadata metadataCopied = copyBuilder.build();
+
+        assertWithMessage("Metadata with the same contents")
+                .that(metadataCopied).isEqualTo(metadata);
+    }
+
+    @Test
+    public void describeContents_forMetadata() {
+        RadioMetadata metadata = mBuilder.build();
+
+        assertWithMessage("Metadata contents").that(metadata.describeContents()).isEqualTo(0);
+    }
+
+    @Test
+    public void newArray_forRadioMetadataCreator() {
+        RadioMetadata[] metadataArray = RadioMetadata.CREATOR.newArray(CREATOR_ARRAY_SIZE);
+
+        assertWithMessage("Radio metadata array").that(metadataArray).hasLength(CREATOR_ARRAY_SIZE);
+    }
+
+    @Test
+    public void writeToParcel_forRadioMetadata() {
+        RadioMetadata metadataExpected = mBuilder
+                .putInt(RadioMetadata.METADATA_KEY_RDS_PI, INT_KEY_VALUE)
+                .putString(RadioMetadata.METADATA_KEY_ARTIST, "artistTest")
+                .build();
+        Parcel parcel = Parcel.obtain();
+
+        metadataExpected.writeToParcel(parcel, /* flags= */ 0);
+        parcel.setDataPosition(0);
+
+        RadioMetadata metadataFromParcel = RadioMetadata.CREATOR.createFromParcel(parcel);
+        assertWithMessage("Radio metadata created from parcel")
+                .that(metadataFromParcel).isEqualTo(metadataExpected);
+    }
 }
diff --git a/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
new file mode 100644
index 0000000..3119554
--- /dev/null
+++ b/core/tests/BroadcastRadioTests/src/com/android/server/broadcastradio/aidl/ConversionUtilsTest.java
@@ -0,0 +1,221 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.broadcastradio.aidl;
+
+import android.hardware.broadcastradio.AmFmBandRange;
+import android.hardware.broadcastradio.AmFmRegionConfig;
+import android.hardware.broadcastradio.DabTableEntry;
+import android.hardware.broadcastradio.IdentifierType;
+import android.hardware.broadcastradio.Properties;
+import android.hardware.broadcastradio.VendorKeyValue;
+import android.hardware.radio.Announcement;
+import android.hardware.radio.ProgramSelector;
+import android.hardware.radio.RadioManager;
+
+import com.google.common.truth.Expect;
+
+import org.junit.Rule;
+import org.junit.Test;
+
+import java.util.Map;
+
+public final class ConversionUtilsTest {
+
+    private static final int FM_LOWER_LIMIT = 87500;
+    private static final int FM_UPPER_LIMIT = 108000;
+    private static final int FM_SPACING = 200;
+    private static final int AM_LOWER_LIMIT = 540;
+    private static final int AM_UPPER_LIMIT = 1700;
+    private static final int AM_SPACING = 10;
+    private static final String DAB_ENTRY_LABEL_1 = "5A";
+    private static final int DAB_ENTRY_FREQUENCY_1 = 174928;
+    private static final String DAB_ENTRY_LABEL_2 = "12D";
+    private static final int DAB_ENTRY_FREQUENCY_2 = 229072;
+    private static final String VENDOR_INFO_KEY_1 = "vendorKey1";
+    private static final String VENDOR_INFO_VALUE_1 = "vendorValue1";
+    private static final String VENDOR_INFO_KEY_2 = "vendorKey2";
+    private static final String VENDOR_INFO_VALUE_2 = "vendorValue2";
+    private static final String TEST_SERVICE_NAME = "serviceMock";
+    private static final int TEST_ID = 1;
+    private static final String TEST_MAKER = "makerMock";
+    private static final String TEST_PRODUCT = "productMock";
+    private static final String TEST_VERSION = "versionMock";
+    private static final String TEST_SERIAL = "serialMock";
+
+    private static final int TEST_ENABLED_TYPE = Announcement.TYPE_EMERGENCY;
+    private static final int TEST_ANNOUNCEMENT_FREQUENCY = FM_LOWER_LIMIT + FM_SPACING;
+
+    private static final RadioManager.ModuleProperties MODULE_PROPERTIES =
+            convertToModuleProperties();
+    private static final Announcement ANNOUNCEMENT =
+            ConversionUtils.announcementFromHalAnnouncement(
+                    AidlTestUtils.makeAnnouncement(TEST_ENABLED_TYPE, TEST_ANNOUNCEMENT_FREQUENCY));
+
+    @Rule
+    public final Expect expect = Expect.create();
+
+    @Test
+    public void propertiesFromHalProperties_idsMatch() {
+        expect.withMessage("Properties id")
+                .that(MODULE_PROPERTIES.getId()).isEqualTo(TEST_ID);
+    }
+
+    @Test
+    public void propertiesFromHalProperties_serviceNamesMatch() {
+        expect.withMessage("Service name")
+                .that(MODULE_PROPERTIES.getServiceName()).isEqualTo(TEST_SERVICE_NAME);
+    }
+
+    @Test
+    public void propertiesFromHalProperties_implementorsMatch() {
+        expect.withMessage("Implementor")
+                .that(MODULE_PROPERTIES.getImplementor()).isEqualTo(TEST_MAKER);
+    }
+
+
+    @Test
+    public void propertiesFromHalProperties_productsMatch() {
+        expect.withMessage("Product")
+                .that(MODULE_PROPERTIES.getProduct()).isEqualTo(TEST_PRODUCT);
+    }
+
+    @Test
+    public void propertiesFromHalProperties_versionsMatch() {
+        expect.withMessage("Version")
+                .that(MODULE_PROPERTIES.getVersion()).isEqualTo(TEST_VERSION);
+    }
+
+    @Test
+    public void propertiesFromHalProperties_serialsMatch() {
+        expect.withMessage("Serial")
+                .that(MODULE_PROPERTIES.getSerial()).isEqualTo(TEST_SERIAL);
+    }
+
+    @Test
+    public void propertiesFromHalProperties_dabTableInfoMatch() {
+        Map<String, Integer> dabTableExpected = Map.of(DAB_ENTRY_LABEL_1, DAB_ENTRY_FREQUENCY_1,
+                DAB_ENTRY_LABEL_2, DAB_ENTRY_FREQUENCY_2);
+
+        expect.withMessage("Supported program types")
+                .that(MODULE_PROPERTIES.getDabFrequencyTable())
+                .containsExactlyEntriesIn(dabTableExpected);
+    }
+
+    @Test
+    public void propertiesFromHalProperties_vendorInfoMatch() {
+        Map<String, String> vendorInfoExpected = Map.of(VENDOR_INFO_KEY_1, VENDOR_INFO_VALUE_1,
+                VENDOR_INFO_KEY_2, VENDOR_INFO_VALUE_2);
+
+        expect.withMessage("Vendor info").that(MODULE_PROPERTIES.getVendorInfo())
+                .containsExactlyEntriesIn(vendorInfoExpected);
+    }
+
+    @Test
+    public void propertiesFromHalProperties_bandsMatch() {
+        RadioManager.BandDescriptor[] bands = MODULE_PROPERTIES.getBands();
+
+        expect.withMessage("Band descriptors").that(bands).hasLength(2);
+
+        expect.withMessage("FM band frequency lower limit")
+                .that(bands[0].getLowerLimit()).isEqualTo(FM_LOWER_LIMIT);
+        expect.withMessage("FM band frequency upper limit")
+                .that(bands[0].getUpperLimit()).isEqualTo(FM_UPPER_LIMIT);
+        expect.withMessage("FM band frequency spacing")
+                .that(bands[0].getSpacing()).isEqualTo(FM_SPACING);
+
+        expect.withMessage("AM band frequency lower limit")
+                .that(bands[1].getLowerLimit()).isEqualTo(AM_LOWER_LIMIT);
+        expect.withMessage("AM band frequency upper limit")
+                .that(bands[1].getUpperLimit()).isEqualTo(AM_UPPER_LIMIT);
+        expect.withMessage("AM band frequency spacing")
+                .that(bands[1].getSpacing()).isEqualTo(AM_SPACING);
+    }
+
+    @Test
+    public void announcementFromHalAnnouncement_typesMatch() {
+        expect.withMessage("Announcement type")
+                .that(ANNOUNCEMENT.getType()).isEqualTo(TEST_ENABLED_TYPE);
+    }
+
+    @Test
+    public void announcementFromHalAnnouncement_selectorsMatch() {
+        ProgramSelector.Identifier primaryIdExpected = new ProgramSelector.Identifier(
+                ProgramSelector.IDENTIFIER_TYPE_AMFM_FREQUENCY, TEST_ANNOUNCEMENT_FREQUENCY);
+
+        ProgramSelector selector = ANNOUNCEMENT.getSelector();
+
+        expect.withMessage("Primary id of announcement selector")
+                .that(selector.getPrimaryId()).isEqualTo(primaryIdExpected);
+        expect.withMessage("Secondary ids of announcement selector")
+                .that(selector.getSecondaryIds()).isEmpty();
+    }
+
+    @Test
+    public void announcementFromHalAnnouncement_VendorInfoMatch() {
+        expect.withMessage("Announcement vendor info")
+                .that(ANNOUNCEMENT.getVendorInfo()).isEmpty();
+    }
+
+    private static RadioManager.ModuleProperties convertToModuleProperties() {
+        AmFmRegionConfig amFmConfig = createAmFmRegionConfig();
+        DabTableEntry[] dabTableEntries = new DabTableEntry[]{
+                createDabTableEntry(DAB_ENTRY_LABEL_1, DAB_ENTRY_FREQUENCY_1),
+                createDabTableEntry(DAB_ENTRY_LABEL_2, DAB_ENTRY_FREQUENCY_2)};
+        Properties properties = createHalProperties();
+
+        return ConversionUtils.propertiesFromHalProperties(TEST_ID, TEST_SERVICE_NAME, properties,
+                amFmConfig, dabTableEntries);
+    }
+
+    private static AmFmRegionConfig createAmFmRegionConfig() {
+        AmFmRegionConfig amFmRegionConfig = new AmFmRegionConfig();
+        amFmRegionConfig.ranges = new AmFmBandRange[]{
+                createAmFmBandRange(FM_LOWER_LIMIT, FM_UPPER_LIMIT, FM_SPACING),
+                createAmFmBandRange(AM_LOWER_LIMIT, AM_UPPER_LIMIT, AM_SPACING)};
+        return amFmRegionConfig;
+    }
+
+    private static AmFmBandRange createAmFmBandRange(int lowerBound, int upperBound, int spacing) {
+        AmFmBandRange bandRange = new AmFmBandRange();
+        bandRange.lowerBound = lowerBound;
+        bandRange.upperBound = upperBound;
+        bandRange.spacing = spacing;
+        bandRange.seekSpacing = bandRange.spacing;
+        return bandRange;
+    }
+
+    private static DabTableEntry createDabTableEntry(String label, int value) {
+        DabTableEntry dabTableEntry = new DabTableEntry();
+        dabTableEntry.label = label;
+        dabTableEntry.frequencyKhz = value;
+        return dabTableEntry;
+    }
+
+    private static Properties createHalProperties() {
+        Properties halProperties = new Properties();
+        halProperties.supportedIdentifierTypes = new int[]{IdentifierType.AMFM_FREQUENCY_KHZ,
+                IdentifierType.RDS_PI, IdentifierType.DAB_SID_EXT};
+        halProperties.maker = TEST_MAKER;
+        halProperties.product = TEST_PRODUCT;
+        halProperties.version = TEST_VERSION;
+        halProperties.serial = TEST_SERIAL;
+        halProperties.vendorInfo = new VendorKeyValue[]{
+                AidlTestUtils.makeVendorKeyValue(VENDOR_INFO_KEY_1, VENDOR_INFO_VALUE_1),
+                AidlTestUtils.makeVendorKeyValue(VENDOR_INFO_KEY_2, VENDOR_INFO_VALUE_2)};
+        return halProperties;
+    }
+}
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index a767f83..48cfc87 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -82,6 +82,7 @@
 
     resource_dirs: ["res"],
     resource_zips: [":FrameworksCoreTests_apks_as_resources"],
+    java_resources: [":ApkVerityTestCertDer"],
 
     data: [
         ":BstatsTestApp",
diff --git a/core/tests/coretests/OWNERS b/core/tests/coretests/OWNERS
index 0fb0c30..e8c9fe7 100644
--- a/core/tests/coretests/OWNERS
+++ b/core/tests/coretests/OWNERS
@@ -1 +1,4 @@
 include platform/frameworks/base:/services/core/java/com/android/server/am/OWNERS
+
+per-file BinderTest.java = file:platform/frameworks/native:/libs/binder/OWNERS
+per-file ParcelTest.java = file:platform/frameworks/native:/libs/binder/OWNERS
diff --git a/core/tests/coretests/res/raw/fsverity_sig b/core/tests/coretests/res/raw/fsverity_sig
new file mode 100644
index 0000000..b2f335d
--- /dev/null
+++ b/core/tests/coretests/res/raw/fsverity_sig
Binary files differ
diff --git a/core/tests/coretests/src/android/app/activity/BroadcastTest.java b/core/tests/coretests/src/android/app/activity/BroadcastTest.java
index 0f81896..7e875ad 100644
--- a/core/tests/coretests/src/android/app/activity/BroadcastTest.java
+++ b/core/tests/coretests/src/android/app/activity/BroadcastTest.java
@@ -18,6 +18,8 @@
 
 import android.app.Activity;
 import android.app.ActivityManager;
+import android.app.BroadcastOptions;
+import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -536,4 +538,40 @@
             Log.i("foo", "Unregister exception", e);
         }
     }
+
+    public void testBroadcastOption_interactive() throws Exception {
+        final BroadcastOptions options = BroadcastOptions.makeBasic();
+        options.setInteractiveBroadcast(true);
+        final Intent intent = makeBroadcastIntent(BROADCAST_REGISTERED);
+
+        try {
+            getContext().sendBroadcast(intent, null, options.toBundle());
+            fail("No exception thrown with BroadcastOptions.setInteractiveBroadcast(true)");
+        } catch (SecurityException se) {
+            // Expected, correct behavior - this case intentionally empty
+        } catch (Exception e) {
+            fail("Unexpected exception " + e.getMessage()
+                    + " thrown with BroadcastOptions.setInteractiveBroadcast(true)");
+        }
+    }
+
+    public void testBroadcastOption_interactive_PendingIntent() throws Exception {
+        final BroadcastOptions options = BroadcastOptions.makeBasic();
+        options.setInteractiveBroadcast(true);
+        final Intent intent = makeBroadcastIntent(BROADCAST_REGISTERED);
+        PendingIntent brPending = PendingIntent.getBroadcast(getContext(),
+                1, intent, PendingIntent.FLAG_IMMUTABLE);
+
+        try {
+            brPending.send(getContext(), 1, null, null, null, null, options.toBundle());
+            fail("No exception thrown with BroadcastOptions.setInteractiveBroadcast(true)");
+        } catch (SecurityException se) {
+            // Expected, correct behavior - this case intentionally empty
+        } catch (Exception e) {
+            fail("Unexpected exception " + e.getMessage()
+                    + " thrown with BroadcastOptions.setInteractiveBroadcast(true)");
+        } finally {
+            brPending.cancel();
+        }
+    }
 }
diff --git a/core/tests/coretests/src/android/app/activity/OWNERS b/core/tests/coretests/src/android/app/activity/OWNERS
index 0862c05..7e24aef 100644
--- a/core/tests/coretests/src/android/app/activity/OWNERS
+++ b/core/tests/coretests/src/android/app/activity/OWNERS
@@ -1 +1,2 @@
 include /services/core/java/com/android/server/wm/OWNERS
+include /services/core/java/com/android/server/am/OWNERS
diff --git a/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java b/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
new file mode 100644
index 0000000..67b24ec
--- /dev/null
+++ b/core/tests/coretests/src/android/app/backup/BackupRestoreEventLoggerTest.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.backup;
+
+import static android.app.backup.BackupRestoreEventLogger.OperationType.BACKUP;
+import static android.app.backup.BackupRestoreEventLogger.OperationType.RESTORE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static junit.framework.Assert.fail;
+
+import android.app.backup.BackupRestoreEventLogger.BackupRestoreDataType;
+import android.app.backup.BackupRestoreEventLogger.DataTypeResult;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.nio.charset.StandardCharsets;
+import java.security.MessageDigest;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class BackupRestoreEventLoggerTest {
+    private static final int DATA_TYPES_ALLOWED = 15;
+
+    private static final String DATA_TYPE_1 = "data_type_1";
+    private static final String DATA_TYPE_2 = "data_type_2";
+    private static final String ERROR_1 = "error_1";
+    private static final String ERROR_2 = "error_2";
+    private static final String METADATA_1 = "metadata_1";
+    private static final String METADATA_2 = "metadata_2";
+
+    private BackupRestoreEventLogger mLogger;
+    private MessageDigest mHashDigest;
+
+    @Before
+    public void setUp() throws Exception {
+        mHashDigest = MessageDigest.getInstance("SHA-256");
+    }
+
+    @Test
+    public void testBackupLogger_rejectsRestoreLogs() {
+        mLogger = new BackupRestoreEventLogger(BACKUP);
+
+        assertThat(mLogger.logItemsRestored(DATA_TYPE_1, /* count */ 5)).isFalse();
+        assertThat(mLogger.logItemsRestoreFailed(DATA_TYPE_1, /* count */ 5, ERROR_1)).isFalse();
+        assertThat(mLogger.logRestoreMetadata(DATA_TYPE_1, /* metadata */ "metadata")).isFalse();
+    }
+
+    @Test
+    public void testRestoreLogger_rejectsBackupLogs() {
+        mLogger = new BackupRestoreEventLogger(RESTORE);
+
+        assertThat(mLogger.logItemsBackedUp(DATA_TYPE_1, /* count */ 5)).isFalse();
+        assertThat(mLogger.logItemsBackupFailed(DATA_TYPE_1, /* count */ 5, ERROR_1)).isFalse();
+        assertThat(mLogger.logBackupMetaData(DATA_TYPE_1, /* metadata */ "metadata")).isFalse();
+    }
+
+    @Test
+    public void testBackupLogger_onlyAcceptsAllowedNumberOfDataTypes() {
+        mLogger = new BackupRestoreEventLogger(BACKUP);
+
+        for (int i = 0; i < DATA_TYPES_ALLOWED; i++) {
+            String dataType = DATA_TYPE_1 + i;
+            assertThat(mLogger.logItemsBackedUp(dataType, /* count */ 5)).isTrue();
+            assertThat(mLogger.logItemsBackupFailed(dataType, /* count */ 5, /* error */ null))
+                    .isTrue();
+            assertThat(mLogger.logBackupMetaData(dataType, METADATA_1)).isTrue();
+        }
+
+        assertThat(mLogger.logItemsBackedUp(DATA_TYPE_2, /* count */ 5)).isFalse();
+        assertThat(mLogger.logItemsBackupFailed(DATA_TYPE_2, /* count */ 5, /* error */ null))
+                .isFalse();
+        assertThat(mLogger.logRestoreMetadata(DATA_TYPE_2, METADATA_1)).isFalse();
+        assertThat(getResultForDataTypeIfPresent(mLogger, DATA_TYPE_2)).isEqualTo(Optional.empty());
+    }
+
+    @Test
+    public void testRestoreLogger_onlyAcceptsAllowedNumberOfDataTypes() {
+        mLogger = new BackupRestoreEventLogger(RESTORE);
+
+        for (int i = 0; i < DATA_TYPES_ALLOWED; i++) {
+            String dataType = DATA_TYPE_1 + i;
+            assertThat(mLogger.logItemsRestored(dataType, /* count */ 5)).isTrue();
+            assertThat(mLogger.logItemsRestoreFailed(dataType, /* count */ 5, /* error */ null))
+                    .isTrue();
+            assertThat(mLogger.logRestoreMetadata(dataType, METADATA_1)).isTrue();
+        }
+
+        assertThat(mLogger.logItemsRestored(DATA_TYPE_2, /* count */ 5)).isFalse();
+        assertThat(mLogger.logItemsRestoreFailed(DATA_TYPE_2, /* count */ 5, /* error */ null))
+                .isFalse();
+        assertThat(mLogger.logRestoreMetadata(DATA_TYPE_2, METADATA_1)).isFalse();
+        assertThat(getResultForDataTypeIfPresent(mLogger, DATA_TYPE_2)).isEqualTo(Optional.empty());
+    }
+
+    @Test
+    public void testLogBackupMetadata_repeatedCalls_recordsLatestMetadataHash() {
+        mLogger = new BackupRestoreEventLogger(BACKUP);
+
+        mLogger.logBackupMetaData(DATA_TYPE_1, METADATA_1);
+        mLogger.logBackupMetaData(DATA_TYPE_1, METADATA_2);
+
+        byte[] recordedHash = getResultForDataType(mLogger, DATA_TYPE_1).getMetadataHash();
+        byte[] expectedHash = getMetaDataHash(METADATA_2);
+        assertThat(Arrays.equals(recordedHash, expectedHash)).isTrue();
+    }
+
+    @Test
+    public void testLogRestoreMetadata_repeatedCalls_recordsLatestMetadataHash() {
+        mLogger = new BackupRestoreEventLogger(RESTORE);
+
+        mLogger.logRestoreMetadata(DATA_TYPE_1, METADATA_1);
+        mLogger.logRestoreMetadata(DATA_TYPE_1, METADATA_2);
+
+        byte[] recordedHash = getResultForDataType(mLogger, DATA_TYPE_1).getMetadataHash();
+        byte[] expectedHash = getMetaDataHash(METADATA_2);
+        assertThat(Arrays.equals(recordedHash, expectedHash)).isTrue();
+    }
+
+    @Test
+    public void testLogItemsBackedUp_repeatedCalls_recordsTotalItems() {
+        mLogger = new BackupRestoreEventLogger(BACKUP);
+
+        int firstCount = 10;
+        int secondCount = 5;
+        mLogger.logItemsBackedUp(DATA_TYPE_1, firstCount);
+        mLogger.logItemsBackedUp(DATA_TYPE_1, secondCount);
+
+        int dataTypeCount = getResultForDataType(mLogger, DATA_TYPE_1).getSuccessCount();
+        assertThat(dataTypeCount).isEqualTo(firstCount + secondCount);
+    }
+
+    @Test
+    public void testLogItemsRestored_repeatedCalls_recordsTotalItems() {
+        mLogger = new BackupRestoreEventLogger(RESTORE);
+
+        int firstCount = 10;
+        int secondCount = 5;
+        mLogger.logItemsRestored(DATA_TYPE_1, firstCount);
+        mLogger.logItemsRestored(DATA_TYPE_1, secondCount);
+
+        int dataTypeCount = getResultForDataType(mLogger, DATA_TYPE_1).getSuccessCount();
+        assertThat(dataTypeCount).isEqualTo(firstCount + secondCount);
+    }
+
+    @Test
+    public void testLogItemsBackedUp_multipleDataTypes_recordsEachDataType() {
+        mLogger = new BackupRestoreEventLogger(BACKUP);
+
+        int firstCount = 10;
+        int secondCount = 5;
+        mLogger.logItemsBackedUp(DATA_TYPE_1, firstCount);
+        mLogger.logItemsBackedUp(DATA_TYPE_2, secondCount);
+
+        int firstDataTypeCount = getResultForDataType(mLogger, DATA_TYPE_1).getSuccessCount();
+        int secondDataTypeCount = getResultForDataType(mLogger, DATA_TYPE_2).getSuccessCount();
+        assertThat(firstDataTypeCount).isEqualTo(firstCount);
+        assertThat(secondDataTypeCount).isEqualTo(secondCount);
+    }
+
+    @Test
+    public void testLogItemsRestored_multipleDataTypes_recordsEachDataType() {
+        mLogger = new BackupRestoreEventLogger(RESTORE);
+
+        int firstCount = 10;
+        int secondCount = 5;
+        mLogger.logItemsRestored(DATA_TYPE_1, firstCount);
+        mLogger.logItemsRestored(DATA_TYPE_2, secondCount);
+
+        int firstDataTypeCount = getResultForDataType(mLogger, DATA_TYPE_1).getSuccessCount();
+        int secondDataTypeCount = getResultForDataType(mLogger, DATA_TYPE_2).getSuccessCount();
+        assertThat(firstDataTypeCount).isEqualTo(firstCount);
+        assertThat(secondDataTypeCount).isEqualTo(secondCount);
+    }
+
+    @Test
+    public void testLogItemsBackupFailed_repeatedCalls_recordsTotalItems() {
+        mLogger = new BackupRestoreEventLogger(BACKUP);
+
+        int firstCount = 10;
+        int secondCount = 5;
+        mLogger.logItemsBackupFailed(DATA_TYPE_1, firstCount, /* error */ null);
+        mLogger.logItemsBackupFailed(DATA_TYPE_1, secondCount, "error");
+
+        int dataTypeCount = getResultForDataType(mLogger, DATA_TYPE_1).getFailCount();
+        assertThat(dataTypeCount).isEqualTo(firstCount + secondCount);
+    }
+
+    @Test
+    public void testLogItemsRestoreFailed_repeatedCalls_recordsTotalItems() {
+        mLogger = new BackupRestoreEventLogger(RESTORE);
+
+        int firstCount = 10;
+        int secondCount = 5;
+        mLogger.logItemsRestoreFailed(DATA_TYPE_1, firstCount, /* error */ null);
+        mLogger.logItemsRestoreFailed(DATA_TYPE_1, secondCount, "error");
+
+        int dataTypeCount = getResultForDataType(mLogger, DATA_TYPE_1).getFailCount();
+        assertThat(dataTypeCount).isEqualTo(firstCount + secondCount);
+    }
+
+    @Test
+    public void testLogItemsBackupFailed_multipleErrors_recordsEachError() {
+        mLogger = new BackupRestoreEventLogger(BACKUP);
+
+        int firstCount = 10;
+        int secondCount = 5;
+        mLogger.logItemsBackupFailed(DATA_TYPE_1, firstCount, ERROR_1);
+        mLogger.logItemsBackupFailed(DATA_TYPE_1, secondCount, ERROR_2);
+
+        int firstErrorTypeCount = getResultForDataType(mLogger, DATA_TYPE_1)
+                .getErrors().get(ERROR_1);
+        int secondErrorTypeCount = getResultForDataType(mLogger, DATA_TYPE_1)
+                .getErrors().get(ERROR_2);
+        assertThat(firstErrorTypeCount).isEqualTo(firstCount);
+        assertThat(secondErrorTypeCount).isEqualTo(secondCount);
+    }
+
+    @Test
+    public void testLogItemsRestoreFailed_multipleErrors_recordsEachError() {
+        mLogger = new BackupRestoreEventLogger(RESTORE);
+
+        int firstCount = 10;
+        int secondCount = 5;
+        mLogger.logItemsRestoreFailed(DATA_TYPE_1, firstCount, ERROR_1);
+        mLogger.logItemsRestoreFailed(DATA_TYPE_1, secondCount, ERROR_2);
+
+        int firstErrorTypeCount = getResultForDataType(mLogger, DATA_TYPE_1)
+                .getErrors().get(ERROR_1);
+        int secondErrorTypeCount = getResultForDataType(mLogger, DATA_TYPE_1)
+                .getErrors().get(ERROR_2);
+        assertThat(firstErrorTypeCount).isEqualTo(firstCount);
+        assertThat(secondErrorTypeCount).isEqualTo(secondCount);
+    }
+
+    private static DataTypeResult getResultForDataType(BackupRestoreEventLogger logger,
+            @BackupRestoreDataType String dataType) {
+        Optional<DataTypeResult> result = getResultForDataTypeIfPresent(logger, dataType);
+        if (result.isEmpty()) {
+            fail("Failed to find result for data type: " + dataType);
+        }
+        return result.get();
+    }
+
+    private static Optional<DataTypeResult> getResultForDataTypeIfPresent(
+            BackupRestoreEventLogger logger, @BackupRestoreDataType String dataType) {
+        List<DataTypeResult> resultList = logger.getLoggingResults();
+        return resultList.stream().filter(
+                dataTypeResult -> dataTypeResult.getDataType().equals(dataType)).findAny();
+    }
+
+    private byte[] getMetaDataHash(String metaData) {
+        return mHashDigest.digest(metaData.getBytes(StandardCharsets.UTF_8));
+    }
+}
diff --git a/core/tests/coretests/src/android/os/ParcelTest.java b/core/tests/coretests/src/android/os/ParcelTest.java
index fdd278b..e2fe87b4 100644
--- a/core/tests/coretests/src/android/os/ParcelTest.java
+++ b/core/tests/coretests/src/android/os/ParcelTest.java
@@ -37,6 +37,13 @@
     private static final String INTERFACE_TOKEN_2 = "Another IBinder interface token";
 
     @Test
+    public void testIsForRpc() {
+        Parcel p = Parcel.obtain();
+        assertEquals(false, p.isForRpc());
+        p.recycle();
+    }
+
+    @Test
     public void testCallingWorkSourceUidAfterWrite() {
         Parcel p = Parcel.obtain();
         // Method does not throw if replaceCallingWorkSourceUid is called before requests headers
diff --git a/core/tests/coretests/src/android/text/method/BackspaceTest.java b/core/tests/coretests/src/android/text/method/BackspaceTest.java
index ddae652..19c2c61 100644
--- a/core/tests/coretests/src/android/text/method/BackspaceTest.java
+++ b/core/tests/coretests/src/android/text/method/BackspaceTest.java
@@ -193,11 +193,15 @@
         backspace(state, 0);
         state.assertEquals("|");
 
-        // Emoji modifier can be appended to the first emoji.
+        // Emoji modifier can be appended to each emoji.
         state.setByString("U+1F469 U+1F3FB U+200D U+1F4BC |");
         backspace(state, 0);
         state.assertEquals("|");
 
+        state.setByString("U+1F468 U+1F3FF U+200D U+2764 U+FE0F U+200D U+1F468 U+1F3FB |");
+        backspace(state, 0);
+        state.assertEquals("|");
+
         // End with ZERO WIDTH JOINER
         state.setByString("U+1F441 U+200D |");
         backspace(state, 0);
diff --git a/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java b/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java
index bb1a3b18..ee1e10f 100644
--- a/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java
+++ b/core/tests/coretests/src/android/view/accessibility/AccessibilityManagerTest.java
@@ -27,6 +27,7 @@
 import static org.mockito.Mockito.when;
 
 import android.accessibilityservice.AccessibilityServiceInfo;
+import android.accessibilityservice.IAccessibilityServiceClient;
 import android.app.Instrumentation;
 import android.app.PendingIntent;
 import android.app.RemoteAction;
@@ -34,6 +35,7 @@
 import android.graphics.drawable.Icon;
 import android.os.UserHandle;
 
+import androidx.annotation.NonNull;
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -51,6 +53,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.concurrent.Executors;
 
 /**
  * Tests for the AccessibilityManager by mocking the backing service.
@@ -70,6 +73,7 @@
             LABEL,
             DESCRIPTION,
             TEST_PENDING_INTENT);
+    private static final int DISPLAY_ID = 22;
 
     @Mock private IAccessibilityManager mMockService;
     private MessageCapturingHandler mHandler;
@@ -224,4 +228,45 @@
         assertEquals(mFocusColorDefaultValue,
                 manager.getAccessibilityFocusColor());
     }
+
+    @Test
+    public void testRegisterAccessibilityProxy() throws Exception {
+        // Accessibility does not need to be enabled for a proxy to be registered.
+        AccessibilityManager manager =
+                new AccessibilityManager(mInstrumentation.getContext(), mHandler, mMockService,
+                        UserHandle.USER_CURRENT, true);
+
+
+        ArrayList<AccessibilityServiceInfo> infos = new ArrayList<>();
+        infos.add(new AccessibilityServiceInfo());
+        AccessibilityDisplayProxy proxy = new MyAccessibilityProxy(DISPLAY_ID, infos);
+        manager.registerDisplayProxy(proxy);
+        // Cannot access proxy.mServiceClient directly due to visibility.
+        verify(mMockService).registerProxyForDisplay(any(IAccessibilityServiceClient.class),
+                any(Integer.class));
+    }
+
+    @Test
+    public void testUnregisterAccessibilityProxy() throws Exception {
+        // Accessibility does not need to be enabled for a proxy to be registered.
+        final AccessibilityManager manager =
+                new AccessibilityManager(mInstrumentation.getContext(), mHandler, mMockService,
+                        UserHandle.USER_CURRENT, true);
+
+        final ArrayList<AccessibilityServiceInfo> infos = new ArrayList<>();
+        infos.add(new AccessibilityServiceInfo());
+
+        final AccessibilityDisplayProxy proxy = new MyAccessibilityProxy(DISPLAY_ID, infos);
+        manager.registerDisplayProxy(proxy);
+        manager.unregisterDisplayProxy(proxy);
+        verify(mMockService).unregisterProxyForDisplay(proxy.getDisplayId());
+    }
+
+    private class MyAccessibilityProxy extends AccessibilityDisplayProxy {
+        // TODO(241429275): Will override A11yProxy methods in the future.
+        MyAccessibilityProxy(int displayId,
+                @NonNull List<AccessibilityServiceInfo> serviceInfos) {
+            super(displayId, Executors.newSingleThreadExecutor(), serviceInfos);
+        }
+    }
 }
diff --git a/core/tests/coretests/src/android/view/inputmethod/ParcelableHandwritingGestureTest.java b/core/tests/coretests/src/android/view/inputmethod/ParcelableHandwritingGestureTest.java
new file mode 100644
index 0000000..79aeaa3
--- /dev/null
+++ b/core/tests/coretests/src/android/view/inputmethod/ParcelableHandwritingGestureTest.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view.inputmethod;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+import android.annotation.NonNull;
+import android.graphics.PointF;
+import android.graphics.RectF;
+import android.os.Parcel;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class ParcelableHandwritingGestureTest {
+
+    @Test
+    public void testCreationFailWithNullPointerException() {
+        assertThrows(NullPointerException.class, () -> ParcelableHandwritingGesture.of(null));
+    }
+
+    @Test
+    public void testInvalidTypeHeader() {
+        Parcel parcel = null;
+        try {
+            parcel = Parcel.obtain();
+            // GESTURE_TYPE_NONE is not a supported header.
+            parcel.writeInt(HandwritingGesture.GESTURE_TYPE_NONE);
+            final Parcel initializedParcel = parcel;
+            assertThrows(UnsupportedOperationException.class,
+                    () -> ParcelableHandwritingGesture.CREATOR.createFromParcel(initializedParcel));
+        } finally {
+            if (parcel != null) {
+                parcel.recycle();
+            }
+        }
+    }
+
+    @Test
+    public void  testSelectGesture() {
+        verifyEqualityAfterUnparcel(new SelectGesture.Builder()
+                .setGranularity(HandwritingGesture.GRANULARITY_WORD)
+                .setSelectionArea(new RectF(1, 2, 3, 4))
+                .setFallbackText("")
+                .build());
+    }
+
+    @Test
+    public void  testSelectRangeGesture() {
+        verifyEqualityAfterUnparcel(new SelectRangeGesture.Builder()
+                .setGranularity(HandwritingGesture.GRANULARITY_WORD)
+                .setSelectionStartArea(new RectF(1, 2, 3, 4))
+                .setSelectionEndArea(new RectF(5, 6, 7, 8))
+                .setFallbackText("")
+                .build());
+    }
+
+    @Test
+    public void  testInsertGestureGesture() {
+        verifyEqualityAfterUnparcel(new InsertGesture.Builder()
+                .setTextToInsert("text")
+                .setInsertionPoint(new PointF(1, 1)).setFallbackText("")
+                .build());
+    }
+
+    @Test
+    public void  testDeleteGestureGesture() {
+        verifyEqualityAfterUnparcel(new DeleteGesture.Builder()
+                .setGranularity(HandwritingGesture.GRANULARITY_WORD)
+                .setDeletionArea(new RectF(1, 2, 3, 4))
+                .setFallbackText("")
+                .build());
+    }
+
+    @Test
+    public void  testDeleteRangeGestureGesture() {
+        verifyEqualityAfterUnparcel(new DeleteRangeGesture.Builder()
+                .setGranularity(HandwritingGesture.GRANULARITY_WORD)
+                .setDeletionStartArea(new RectF(1, 2, 3, 4))
+                .setDeletionEndArea(new RectF(5, 6, 7, 8))
+                .setFallbackText("")
+                .build());
+    }
+
+    @Test
+    public void  testRemoveSpaceGestureGesture() {
+        verifyEqualityAfterUnparcel(new RemoveSpaceGesture.Builder()
+                .setPoints(new PointF(1f, 2f), new PointF(3f, 4f))
+                .setFallbackText("")
+                .build());
+    }
+
+    @Test
+    public void  testJoinOrSplitGestureGesture() {
+        verifyEqualityAfterUnparcel(new JoinOrSplitGesture.Builder()
+                .setJoinOrSplitPoint(new PointF(1f, 2f))
+                .setFallbackText("")
+                .build());
+    }
+
+    static void verifyEqualityAfterUnparcel(@NonNull HandwritingGesture gesture) {
+        assertEquals(gesture, cloneViaParcel(ParcelableHandwritingGesture.of(gesture)).get());
+    }
+
+    private static ParcelableHandwritingGesture cloneViaParcel(
+            @NonNull ParcelableHandwritingGesture original) {
+        Parcel parcel = null;
+        try {
+            parcel = Parcel.obtain();
+            original.writeToParcel(parcel, 0);
+            parcel.setDataPosition(0);
+            return ParcelableHandwritingGesture.CREATOR.createFromParcel(parcel);
+        } finally {
+            if (parcel != null) {
+                parcel.recycle();
+            }
+        }
+    }
+}
diff --git a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
index d4a6632..95aa5d0 100644
--- a/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
+++ b/core/tests/coretests/src/android/view/stylus/HandwritingInitiatorTest.java
@@ -32,6 +32,7 @@
 import android.content.Context;
 import android.graphics.Rect;
 import android.platform.test.annotations.Presubmit;
+import android.view.HandwritingDelegateConfiguration;
 import android.view.HandwritingInitiator;
 import android.view.InputDevice;
 import android.view.MotionEvent;
@@ -208,6 +209,30 @@
     }
 
     @Test
+    public void onTouchEvent_startHandwriting_delegate() {
+        int delegatorViewId = 234;
+        View delegatorView = new View(mContext);
+        delegatorView.setId(delegatorViewId);
+
+        mTestView.setHandwritingDelegateConfiguration(
+                new HandwritingDelegateConfiguration(
+                        delegatorViewId,
+                        () -> mHandwritingInitiator.onInputConnectionCreated(delegatorView)));
+
+        final int x1 = (sHwArea.left + sHwArea.right) / 2;
+        final int y1 = (sHwArea.top + sHwArea.bottom) / 2;
+        MotionEvent stylusEvent1 = createStylusEvent(ACTION_DOWN, x1, y1, 0);
+        mHandwritingInitiator.onTouchEvent(stylusEvent1);
+
+        final int x2 = x1 + mHandwritingSlop * 2;
+        final int y2 = y1;
+        MotionEvent stylusEvent2 = createStylusEvent(ACTION_MOVE, x2, y2, 0);
+        mHandwritingInitiator.onTouchEvent(stylusEvent2);
+
+        verify(mHandwritingInitiator, times(1)).startHandwriting(delegatorView);
+    }
+
+    @Test
     public void onTouchEvent_notStartHandwriting_whenHandwritingNotAvailable() {
         final Rect rect = new Rect(600, 600, 900, 900);
         final View testView = createView(rect, true /* autoHandwritingEnabled */,
diff --git a/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeHandleTest.java b/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeHandleTest.java
new file mode 100644
index 0000000..f111bf6f
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/inputmethod/InputMethodSubtypeHandleTest.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.inputmethod;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThrows;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.os.Parcel;
+import android.platform.test.annotations.Presubmit;
+import android.view.inputmethod.InputMethodInfo;
+import android.view.inputmethod.InputMethodSubtype;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.security.InvalidParameterException;
+
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class InputMethodSubtypeHandleTest {
+
+    @Test
+    public void testCreateFromRawHandle() {
+        {
+            final InputMethodSubtypeHandle handle =
+                    InputMethodSubtypeHandle.of("com.android.test/.Ime1:subtype:1");
+            assertNotNull(handle);
+            assertEquals("com.android.test/.Ime1:subtype:1", handle.toStringHandle());
+            assertEquals("com.android.test/.Ime1", handle.getImeId());
+            assertEquals(ComponentName.unflattenFromString("com.android.test/.Ime1"),
+                    handle.getComponentName());
+        }
+
+        assertThrows(NullPointerException.class, () -> InputMethodSubtypeHandle.of(null));
+        assertThrows(InvalidParameterException.class, () -> InputMethodSubtypeHandle.of(""));
+
+        // The IME ID must use ComponentName#flattenToShortString(), not #flattenToString().
+        assertThrows(InvalidParameterException.class, () -> InputMethodSubtypeHandle.of(
+                "com.android.test/com.android.test.Ime1:subtype:1"));
+
+        assertThrows(InvalidParameterException.class, () -> InputMethodSubtypeHandle.of(
+                "com.android.test/.Ime1:subtype:0001"));
+        assertThrows(InvalidParameterException.class, () -> InputMethodSubtypeHandle.of(
+                "com.android.test/.Ime1:subtype:1!"));
+        assertThrows(InvalidParameterException.class, () -> InputMethodSubtypeHandle.of(
+                "com.android.test/.Ime1:subtype:1:"));
+        assertThrows(InvalidParameterException.class, () -> InputMethodSubtypeHandle.of(
+                "com.android.test/.Ime1:subtype:1:2"));
+        assertThrows(InvalidParameterException.class, () -> InputMethodSubtypeHandle.of(
+                "com.android.test/.Ime1:subtype:a"));
+        assertThrows(InvalidParameterException.class, () -> InputMethodSubtypeHandle.of(
+                "com.android.test/.Ime1:subtype:0x01"));
+        assertThrows(InvalidParameterException.class, () -> InputMethodSubtypeHandle.of(
+                "com.android.test/.Ime1:Subtype:a"));
+        assertThrows(InvalidParameterException.class, () -> InputMethodSubtypeHandle.of(
+                "ime1:subtype:1"));
+    }
+
+    @Test
+    public void testCreateFromInputMethodInfo() {
+        final InputMethodInfo imi = new InputMethodInfo(
+                "com.android.test", "com.android.test.Ime1", "TestIME", null);
+        {
+            final InputMethodSubtypeHandle handle = InputMethodSubtypeHandle.of(imi, null);
+            assertNotNull(handle);
+            assertEquals("com.android.test/.Ime1:subtype:0", handle.toStringHandle());
+            assertEquals("com.android.test/.Ime1", handle.getImeId());
+            assertEquals(ComponentName.unflattenFromString("com.android.test/.Ime1"),
+                    handle.getComponentName());
+        }
+
+        final InputMethodSubtype subtype =
+                new InputMethodSubtype.InputMethodSubtypeBuilder().setSubtypeId(1).build();
+        {
+            final InputMethodSubtypeHandle handle = InputMethodSubtypeHandle.of(imi, subtype);
+            assertNotNull(handle);
+            assertEquals("com.android.test/.Ime1:subtype:1", handle.toStringHandle());
+            assertEquals("com.android.test/.Ime1", handle.getImeId());
+            assertEquals(ComponentName.unflattenFromString("com.android.test/.Ime1"),
+                    handle.getComponentName());
+        }
+
+        assertThrows(NullPointerException.class, () -> InputMethodSubtypeHandle.of(null, null));
+        assertThrows(NullPointerException.class, () -> InputMethodSubtypeHandle.of(null, subtype));
+    }
+
+    @Test
+    public void testEquality() {
+        assertEquals(InputMethodSubtypeHandle.of("com.android.test/.Ime1:subtype:1"),
+                InputMethodSubtypeHandle.of("com.android.test/.Ime1:subtype:1"));
+        assertEquals(InputMethodSubtypeHandle.of("com.android.test/.Ime1:subtype:1").hashCode(),
+                InputMethodSubtypeHandle.of("com.android.test/.Ime1:subtype:1").hashCode());
+
+        assertNotEquals(InputMethodSubtypeHandle.of("com.android.test/.Ime1:subtype:1"),
+                InputMethodSubtypeHandle.of("com.android.test/.Ime1:subtype:2"));
+        assertNotEquals(InputMethodSubtypeHandle.of("com.android.test/.Ime1:subtype:1"),
+                InputMethodSubtypeHandle.of("com.android.test/.Ime2:subtype:1"));
+    }
+
+    @Test
+    public void testParcelablility() {
+        final InputMethodSubtypeHandle original =
+                InputMethodSubtypeHandle.of("com.android.test/.Ime1:subtype:1");
+        final InputMethodSubtypeHandle cloned = cloneHandle(original);
+        assertEquals(original, cloned);
+        assertEquals(original.hashCode(), cloned.hashCode());
+        assertEquals(original.getComponentName(), cloned.getComponentName());
+        assertEquals(original.getImeId(), cloned.getImeId());
+        assertEquals(original.toStringHandle(), cloned.toStringHandle());
+    }
+
+    @Test
+    public void testNoUnnecessaryStringInstantiationInToStringHandle() {
+        final String validHandleStr = "com.android.test/.Ime1:subtype:1";
+        // Verify that toStringHandle() returns the same String object if the input is valid for
+        // an efficient memory usage.
+        assertSame(validHandleStr, InputMethodSubtypeHandle.of(validHandleStr).toStringHandle());
+    }
+
+    @NonNull
+    private static InputMethodSubtypeHandle cloneHandle(
+            @NonNull InputMethodSubtypeHandle original) {
+        Parcel parcel = null;
+        try {
+            parcel = Parcel.obtain();
+            original.writeToParcel(parcel, 0);
+            parcel.setDataPosition(0);
+            return InputMethodSubtypeHandle.CREATOR.createFromParcel(parcel);
+        } finally {
+            if (parcel != null) {
+                parcel.recycle();
+            }
+        }
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/security/ContentSignerWrapper.java b/core/tests/coretests/src/com/android/internal/security/ContentSignerWrapper.java
new file mode 100644
index 0000000..0254afe
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/security/ContentSignerWrapper.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.security;
+
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.operator.ContentSigner;
+
+import java.io.OutputStream;
+
+/** A wrapper class of ContentSigner */
+class ContentSignerWrapper implements ContentSigner {
+    private final ContentSigner mSigner;
+
+    ContentSignerWrapper(ContentSigner wrapped) {
+        mSigner = wrapped;
+    }
+
+    @Override
+    public AlgorithmIdentifier getAlgorithmIdentifier() {
+        return mSigner.getAlgorithmIdentifier();
+    }
+
+    @Override
+    public OutputStream getOutputStream() {
+        return mSigner.getOutputStream();
+    }
+
+    @Override
+    public byte[] getSignature() {
+        return mSigner.getSignature();
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/security/OWNERS b/core/tests/coretests/src/com/android/internal/security/OWNERS
new file mode 100644
index 0000000..4f4d8d7
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/security/OWNERS
@@ -0,0 +1,3 @@
+# Bug component: 36824
+
+per-file VerityUtilsTest.java = file:platform/system/security:/fsverity/OWNERS
diff --git a/core/tests/coretests/src/com/android/internal/security/VerityUtilsTest.java b/core/tests/coretests/src/com/android/internal/security/VerityUtilsTest.java
new file mode 100644
index 0000000..1513654
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/security/VerityUtilsTest.java
@@ -0,0 +1,355 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.security;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.frameworks.coretests.R;
+
+import org.bouncycastle.asn1.ASN1Encoding;
+import org.bouncycastle.asn1.ASN1ObjectIdentifier;
+import org.bouncycastle.asn1.nist.NISTObjectIdentifiers;
+import org.bouncycastle.asn1.oiw.OIWObjectIdentifiers;
+import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
+import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
+import org.bouncycastle.cert.X509CertificateHolder;
+import org.bouncycastle.cert.X509v3CertificateBuilder;
+import org.bouncycastle.cms.CMSException;
+import org.bouncycastle.cms.CMSProcessableByteArray;
+import org.bouncycastle.cms.CMSSignedData;
+import org.bouncycastle.cms.CMSSignedDataGenerator;
+import org.bouncycastle.cms.SignerInfoGenerator;
+import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.DigestCalculator;
+import org.bouncycastle.operator.OperatorCreationException;
+import org.bouncycastle.operator.bc.BcDigestCalculatorProvider;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.PrivateKey;
+import java.security.PublicKey;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Arrays;
+import java.util.Date;
+import java.util.HexFormat;
+
+@Presubmit
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class VerityUtilsTest {
+    private static final byte[] SAMPLE_DIGEST = "12345678901234567890123456789012".getBytes();
+    private static final byte[] FORMATTED_SAMPLE_DIGEST = toFormattedDigest(SAMPLE_DIGEST);
+
+    KeyPair mKeyPair;
+    ContentSigner mContentSigner;
+    X509CertificateHolder mCertificateHolder;
+    byte[] mCertificateDerEncoded;
+
+    @Before
+    public void setUp() throws Exception {
+        mKeyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair();
+        mContentSigner = newFsverityContentSigner(mKeyPair.getPrivate());
+        mCertificateHolder =
+                newX509CertificateHolder(mContentSigner, mKeyPair.getPublic(), "Someone");
+        mCertificateDerEncoded = mCertificateHolder.getEncoded();
+    }
+
+    @Test
+    public void testOnlyAcceptCorrectDigest() throws Exception {
+        byte[] pkcs7Signature =
+                generatePkcs7Signature(mContentSigner, mCertificateHolder, FORMATTED_SAMPLE_DIGEST);
+
+        byte[] anotherDigest = Arrays.copyOf(SAMPLE_DIGEST, SAMPLE_DIGEST.length);
+        anotherDigest[0] ^= (byte) 1;
+
+        assertTrue(verifySignature(pkcs7Signature, SAMPLE_DIGEST, mCertificateDerEncoded));
+        assertFalse(verifySignature(pkcs7Signature, anotherDigest, mCertificateDerEncoded));
+    }
+
+    @Test
+    public void testDigestWithWrongSize() throws Exception {
+        byte[] pkcs7Signature =
+                generatePkcs7Signature(mContentSigner, mCertificateHolder, FORMATTED_SAMPLE_DIGEST);
+        assertTrue(verifySignature(pkcs7Signature, SAMPLE_DIGEST, mCertificateDerEncoded));
+
+        byte[] digestTooShort = Arrays.copyOfRange(SAMPLE_DIGEST, 0, SAMPLE_DIGEST.length - 1);
+        assertFalse(verifySignature(pkcs7Signature, digestTooShort, mCertificateDerEncoded));
+
+        byte[] digestTooLong = Arrays.copyOfRange(SAMPLE_DIGEST, 0, SAMPLE_DIGEST.length + 1);
+        assertFalse(verifySignature(pkcs7Signature, digestTooLong, mCertificateDerEncoded));
+    }
+
+    @Test
+    public void testOnlyAcceptGoodSignature() throws Exception {
+        byte[] pkcs7Signature =
+                generatePkcs7Signature(mContentSigner, mCertificateHolder, FORMATTED_SAMPLE_DIGEST);
+
+        byte[] anotherDigest = Arrays.copyOf(SAMPLE_DIGEST, SAMPLE_DIGEST.length);
+        anotherDigest[0] ^= (byte) 1;
+        byte[] anotherPkcs7Signature =
+                generatePkcs7Signature(
+                        mContentSigner, mCertificateHolder, toFormattedDigest(anotherDigest));
+
+        assertTrue(verifySignature(pkcs7Signature, SAMPLE_DIGEST, mCertificateDerEncoded));
+        assertFalse(verifySignature(anotherPkcs7Signature, SAMPLE_DIGEST, mCertificateDerEncoded));
+    }
+
+    @Test
+    public void testOnlyValidCertCanVerify() throws Exception {
+        byte[] pkcs7Signature =
+                generatePkcs7Signature(mContentSigner, mCertificateHolder, FORMATTED_SAMPLE_DIGEST);
+
+        var wrongKeyPair = KeyPairGenerator.getInstance("RSA").generateKeyPair();
+        var wrongContentSigner = newFsverityContentSigner(wrongKeyPair.getPrivate());
+        var wrongCertificateHolder =
+                newX509CertificateHolder(wrongContentSigner, wrongKeyPair.getPublic(), "Not Me");
+        byte[] wrongCertificateDerEncoded = wrongCertificateHolder.getEncoded();
+
+        assertFalse(verifySignature(pkcs7Signature, SAMPLE_DIGEST, wrongCertificateDerEncoded));
+    }
+
+    @Test
+    public void testRejectSignatureWithContent() throws Exception {
+        CMSSignedDataGenerator generator =
+                newFsveritySignedDataGenerator(mContentSigner, mCertificateHolder);
+        byte[] pkcs7SignatureNonDetached =
+                generatePkcs7SignatureInternal(
+                        generator, FORMATTED_SAMPLE_DIGEST, /* encapsulate */ true);
+
+        assertFalse(
+                verifySignature(pkcs7SignatureNonDetached, SAMPLE_DIGEST, mCertificateDerEncoded));
+    }
+
+    @Test
+    public void testRejectSignatureWithCertificate() throws Exception {
+        CMSSignedDataGenerator generator =
+                newFsveritySignedDataGenerator(mContentSigner, mCertificateHolder);
+        generator.addCertificate(mCertificateHolder);
+        byte[] pkcs7Signature =
+                generatePkcs7SignatureInternal(
+                        generator, FORMATTED_SAMPLE_DIGEST, /* encapsulate */ false);
+
+        assertFalse(
+                verifySignature(pkcs7Signature, SAMPLE_DIGEST, mCertificateDerEncoded));
+    }
+
+    @Ignore("No easy way to construct test data")
+    @Test
+    public void testRejectSignatureWithCRL() throws Exception {
+        CMSSignedDataGenerator generator =
+                newFsveritySignedDataGenerator(mContentSigner, mCertificateHolder);
+
+        // The current bouncycastle version does not have an easy way to generate a CRL.
+        // TODO: enable the test once this is doable, e.g. with X509v2CRLBuilder.
+        // generator.addCRL(new X509CRLHolder(CertificateList.getInstance(new DERSequence(...))));
+        byte[] pkcs7Signature =
+                generatePkcs7SignatureInternal(
+                        generator, FORMATTED_SAMPLE_DIGEST, /* encapsulate */ false);
+
+        assertFalse(
+                verifySignature(pkcs7Signature, SAMPLE_DIGEST, mCertificateDerEncoded));
+    }
+
+    @Test
+    public void testRejectUnsupportedSignatureAlgorithms() throws Exception {
+        var contentSigner = newFsverityContentSigner(mKeyPair.getPrivate(), "MD5withRSA", null);
+        var certificateHolder =
+                newX509CertificateHolder(contentSigner, mKeyPair.getPublic(), "Someone");
+        byte[] pkcs7Signature =
+                generatePkcs7Signature(contentSigner, certificateHolder, FORMATTED_SAMPLE_DIGEST);
+
+        assertFalse(verifySignature(pkcs7Signature, SAMPLE_DIGEST, certificateHolder.getEncoded()));
+    }
+
+    @Test
+    public void testRejectUnsupportedDigestAlgorithm() throws Exception {
+        CMSSignedDataGenerator generator = new CMSSignedDataGenerator();
+        generator.addSignerInfoGenerator(
+                newSignerInfoGenerator(
+                        mContentSigner,
+                        mCertificateHolder,
+                        OIWObjectIdentifiers.idSHA1,
+                        true)); // directSignature
+        byte[] pkcs7Signature =
+                generatePkcs7SignatureInternal(
+                        generator, FORMATTED_SAMPLE_DIGEST, /* encapsulate */ false);
+
+        assertFalse(verifySignature(pkcs7Signature, SAMPLE_DIGEST, mCertificateDerEncoded));
+    }
+
+    @Test
+    public void testRejectAnySignerInfoAttributes() throws Exception {
+        var generator = new CMSSignedDataGenerator();
+        generator.addSignerInfoGenerator(
+                newSignerInfoGenerator(
+                        mContentSigner,
+                        mCertificateHolder,
+                        NISTObjectIdentifiers.id_sha256,
+                        false)); // directSignature
+        byte[] pkcs7Signature =
+                generatePkcs7SignatureInternal(
+                        generator, FORMATTED_SAMPLE_DIGEST, /* encapsulate */ false);
+
+        assertFalse(verifySignature(pkcs7Signature, SAMPLE_DIGEST, mCertificateDerEncoded));
+    }
+
+    @Test
+    public void testSignatureGeneratedExternally() throws Exception {
+        var context = InstrumentationRegistry.getInstrumentation().getContext();
+        byte[] cert = getClass().getClassLoader().getResourceAsStream("ApkVerityTestCert.der")
+                .readAllBytes();
+        // The signature is generated by:
+        //   fsverity sign <(echo -n fs-verity) fsverity_sig --key=ApkVerityTestKey.pem \
+        //   --cert=ApkVerityTestCert.pem
+        byte[] sig = context.getResources().openRawResource(R.raw.fsverity_sig).readAllBytes();
+        // The fs-verity digest is generated by:
+        //   fsverity digest --compact <(echo -n fs-verity)
+        byte[] digest = HexFormat.of().parseHex(
+                "3d248ca542a24fc62d1c43b916eae5016878e2533c88238480b26128a1f1af95");
+
+        assertTrue(verifySignature(sig, digest, cert));
+    }
+
+    private static boolean verifySignature(
+            byte[] pkcs7Signature, byte[] fsverityDigest, byte[] certificateDerEncoded) {
+        return VerityUtils.verifyPkcs7DetachedSignature(
+                pkcs7Signature, fsverityDigest, new ByteArrayInputStream(certificateDerEncoded));
+    }
+
+    private static byte[] toFormattedDigest(byte[] digest) {
+        return VerityUtils.toFormattedDigest(digest);
+    }
+
+    private static byte[] generatePkcs7Signature(
+            ContentSigner contentSigner, X509CertificateHolder certificateHolder, byte[] signedData)
+            throws IOException, CMSException, OperatorCreationException {
+        CMSSignedDataGenerator generator =
+                newFsveritySignedDataGenerator(contentSigner, certificateHolder);
+        return generatePkcs7SignatureInternal(generator, signedData, /* encapsulate */ false);
+    }
+
+    private static byte[] generatePkcs7SignatureInternal(
+            CMSSignedDataGenerator generator, byte[] signedData, boolean encapsulate)
+            throws IOException, CMSException, OperatorCreationException {
+        CMSSignedData cmsSignedData =
+                generator.generate(new CMSProcessableByteArray(signedData), encapsulate);
+        return cmsSignedData.toASN1Structure().getEncoded(ASN1Encoding.DL);
+    }
+
+    private static CMSSignedDataGenerator newFsveritySignedDataGenerator(
+            ContentSigner contentSigner, X509CertificateHolder certificateHolder)
+            throws IOException, CMSException, OperatorCreationException {
+        var generator = new CMSSignedDataGenerator();
+        generator.addSignerInfoGenerator(
+                newSignerInfoGenerator(
+                        contentSigner,
+                        certificateHolder,
+                        NISTObjectIdentifiers.id_sha256,
+                        true)); // directSignature
+        return generator;
+    }
+
+    private static SignerInfoGenerator newSignerInfoGenerator(
+            ContentSigner contentSigner,
+            X509CertificateHolder certificateHolder,
+            ASN1ObjectIdentifier digestAlgorithmId,
+            boolean directSignature)
+            throws IOException, CMSException, OperatorCreationException {
+        var provider =
+                new BcDigestCalculatorProvider() {
+                    /**
+                     * Allow the caller to override the digest algorithm, especially when the
+                     * default does not work (i.e. BcDigestCalculatorProvider could return null).
+                     *
+                     * <p>For example, the current fs-verity signature has to use rsaEncryption for
+                     * the signature algorithm, but BcDigestCalculatorProvider will return null,
+                     * thus we need a way to override.
+                     *
+                     * <p>TODO: After bouncycastle 1.70, we can remove this override and just use
+                     * {@code JcaSignerInfoGeneratorBuilder#setContentDigest}.
+                     */
+                    @Override
+                    public DigestCalculator get(AlgorithmIdentifier algorithm)
+                            throws OperatorCreationException {
+                        return super.get(new AlgorithmIdentifier(digestAlgorithmId));
+                    }
+                };
+        var builder =
+                new JcaSignerInfoGeneratorBuilder(provider).setDirectSignature(directSignature);
+        return builder.build(contentSigner, certificateHolder);
+    }
+
+    private static ContentSigner newFsverityContentSigner(PrivateKey privateKey)
+            throws OperatorCreationException {
+        // fs-verity expects the signature to have rsaEncryption as the exact algorithm, so
+        // override the default.
+        return newFsverityContentSigner(
+                privateKey, "SHA256withRSA", PKCSObjectIdentifiers.rsaEncryption);
+    }
+
+    private static ContentSigner newFsverityContentSigner(
+            PrivateKey privateKey,
+            String signatureAlgorithm,
+            ASN1ObjectIdentifier signatureAlgorithmIdOverride)
+            throws OperatorCreationException {
+        if (signatureAlgorithmIdOverride != null) {
+            return new ContentSignerWrapper(
+                    new JcaContentSignerBuilder(signatureAlgorithm).build(privateKey)) {
+                @Override
+                public AlgorithmIdentifier getAlgorithmIdentifier() {
+                    return new AlgorithmIdentifier(signatureAlgorithmIdOverride);
+                }
+            };
+        } else {
+            return new JcaContentSignerBuilder(signatureAlgorithm).build(privateKey);
+        }
+    }
+
+    private static X509CertificateHolder newX509CertificateHolder(
+            ContentSigner contentSigner, PublicKey publicKey, String name) {
+        // Time doesn't really matter, as we only care about the key.
+        Instant now = Instant.now();
+
+        return new X509v3CertificateBuilder(
+                        new X500Name("CN=Issuer " + name),
+                        /* serial= */ BigInteger.valueOf(now.getEpochSecond()),
+                        new Date(now.minus(Duration.ofDays(1)).toEpochMilli()),
+                        new Date(now.plus(Duration.ofDays(1)).toEpochMilli()),
+                        new X500Name("CN=Subject " + name),
+                        SubjectPublicKeyInfo.getInstance(publicKey.getEncoded()))
+                .build(contentSigner);
+    }
+}
diff --git a/core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java b/core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java
index c53fb23..048c48b 100644
--- a/core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java
+++ b/core/tests/coretests/src/com/android/internal/statusbar/RegisterStatusBarResultTest.java
@@ -25,7 +25,7 @@
 import android.os.Parcel;
 import android.os.UserHandle;
 import android.util.ArrayMap;
-import android.view.InsetsVisibilities;
+import android.view.WindowInsets;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
@@ -65,7 +65,7 @@
                 new Binder() /* imeToken */,
                 true /* navbarColorManagedByIme */,
                 BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE,
-                new InsetsVisibilities() /* requestedVisibilities */,
+                WindowInsets.Type.defaultVisible(),
                 "test" /* packageName */,
                 new int[0] /* transientBarTypes */,
                 new LetterboxDetails[] {letterboxDetails});
@@ -87,7 +87,7 @@
         assertThat(copy.mImeToken).isSameInstanceAs(original.mImeToken);
         assertThat(copy.mNavbarColorManagedByIme).isEqualTo(original.mNavbarColorManagedByIme);
         assertThat(copy.mBehavior).isEqualTo(original.mBehavior);
-        assertThat(copy.mRequestedVisibilities).isEqualTo(original.mRequestedVisibilities);
+        assertThat(copy.mRequestedVisibleTypes).isEqualTo(original.mRequestedVisibleTypes);
         assertThat(copy.mPackageName).isEqualTo(original.mPackageName);
         assertThat(copy.mTransientBarTypes).isEqualTo(original.mTransientBarTypes);
         assertThat(copy.mLetterboxDetails).isEqualTo(original.mLetterboxDetails);
diff --git a/graphics/java/android/view/PixelCopy.java b/graphics/java/android/view/PixelCopy.java
index 0f82c8f..889edb3 100644
--- a/graphics/java/android/view/PixelCopy.java
+++ b/graphics/java/android/view/PixelCopy.java
@@ -311,11 +311,11 @@
     /**
      * Contains the result of a PixelCopy request
      */
-    public static final class CopyResult {
+    public static final class Result {
         private int mStatus;
         private Bitmap mBitmap;
 
-        private CopyResult(@CopyResultStatus int status, Bitmap bitmap) {
+        private Result(@CopyResultStatus int status, Bitmap bitmap) {
             mStatus = status;
             mBitmap = bitmap;
         }
@@ -335,8 +335,8 @@
 
         /**
          * If the PixelCopy {@link Request} was given a destination bitmap with
-         * {@link Request#setDestinationBitmap(Bitmap)} then the returned bitmap will be the same
-         * as the one given. If no destination bitmap was provided, then this
+         * {@link Request.Builder#setDestinationBitmap(Bitmap)} then the returned bitmap will be
+         * the same as the one given. If no destination bitmap was provided, then this
          * will contain the automatically allocated Bitmap to hold the result.
          *
          * @return the Bitmap the copy request was stored in.
@@ -349,66 +349,199 @@
     }
 
     /**
-     * A builder to create the complete PixelCopy request, which is then executed by calling
-     * {@link #request()}
+     * Represents a PixelCopy request.
+     *
+     * To create a copy request, use either of the PixelCopy.Request.ofWindow or
+     * PixelCopy.Request.ofSurface factories to create a {@link Request.Builder} for the
+     * given source content. After setting any optional parameters, such as
+     * {@link Builder#setSourceRect(Rect)}, build the request with {@link Builder#build()} and
+     * then execute it with {@link PixelCopy#request(Request)}
      */
     public static final class Request {
+        private final Surface mSource;
+        private final Consumer<Result> mListener;
+        private final Executor mListenerThread;
+        private final Rect mSourceInsets;
+        private Rect mSrcRect;
+        private Bitmap mDest;
+
         private Request(Surface source, Rect sourceInsets, Executor listenerThread,
-                        Consumer<CopyResult> listener) {
+                        Consumer<Result> listener) {
             this.mSource = source;
             this.mSourceInsets = sourceInsets;
             this.mListenerThread = listenerThread;
             this.mListener = listener;
         }
 
-        private final Surface mSource;
-        private final Consumer<CopyResult> mListener;
-        private final Executor mListenerThread;
-        private final Rect mSourceInsets;
-        private Rect mSrcRect;
-        private Bitmap mDest;
-
         /**
-         * Sets the region of the source to copy from. By default, the entire source is copied to
-         * the output. If only a subset of the source is necessary to be copied, specifying a
-         * srcRect will improve performance by reducing
-         * the amount of data being copied.
-         *
-         * @param srcRect The area of the source to read from. Null or empty will be treated to
-         *                mean the entire source
-         * @return this
+         * A builder to create the complete PixelCopy request, which is then executed by calling
+         * {@link #request(Request)} with the built request returned from {@link #build()}
          */
-        public @NonNull Request setSourceRect(@Nullable Rect srcRect) {
-            this.mSrcRect = srcRect;
-            return this;
-        }
+        public static final class Builder {
+            private Request mRequest;
 
-        /**
-         * Specifies the output bitmap in which to store the result. By default, a Bitmap of format
-         * {@link android.graphics.Bitmap.Config#ARGB_8888} with a width & height matching that
-         * of the {@link #setSourceRect(Rect) source area} will be created to place the result.
-         *
-         * @param destination The bitmap to store the result, or null to have a bitmap
-         *                    automatically created of the appropriate size. If not null, must not
-         *                    be {@link Bitmap#isRecycled() recycled} and must be
-         *                    {@link Bitmap#isMutable() mutable}.
-         * @return this
-         */
-        public @NonNull Request setDestinationBitmap(@Nullable Bitmap destination) {
-            if (destination != null) {
-                validateBitmapDest(destination);
+            private Builder(Request request) {
+                mRequest = request;
             }
-            this.mDest = destination;
-            return this;
+
+            private void requireNotBuilt() {
+                if (mRequest == null) {
+                    throw new IllegalStateException("build() already called on this builder");
+                }
+            }
+
+            /**
+             * Sets the region of the source to copy from. By default, the entire source is copied
+             * to the output. If only a subset of the source is necessary to be copied, specifying
+             * a srcRect will improve performance by reducing
+             * the amount of data being copied.
+             *
+             * @param srcRect The area of the source to read from. Null or empty will be treated to
+             *                mean the entire source
+             * @return this
+             */
+            public @NonNull Builder setSourceRect(@Nullable Rect srcRect) {
+                requireNotBuilt();
+                mRequest.mSrcRect = srcRect;
+                return this;
+            }
+
+            /**
+             * Specifies the output bitmap in which to store the result. By default, a Bitmap of
+             * format {@link android.graphics.Bitmap.Config#ARGB_8888} with a width & height
+             * matching that of the {@link #setSourceRect(Rect) source area} will be created to
+             * place the result.
+             *
+             * @param destination The bitmap to store the result, or null to have a bitmap
+             *                    automatically created of the appropriate size. If not null, must
+             *                    not be {@link Bitmap#isRecycled() recycled} and must be
+             *                    {@link Bitmap#isMutable() mutable}.
+             * @return this
+             */
+            public @NonNull Builder setDestinationBitmap(@Nullable Bitmap destination) {
+                requireNotBuilt();
+                if (destination != null) {
+                    validateBitmapDest(destination);
+                }
+                mRequest.mDest = destination;
+                return this;
+            }
+
+            /**
+             * @return The built {@link PixelCopy.Request}
+             */
+            public @NonNull Request build() {
+                requireNotBuilt();
+                Request ret = mRequest;
+                mRequest = null;
+                return ret;
+            }
         }
 
         /**
-         * Executes the request.
+         * Creates a PixelCopy request for the given {@link Window}
+         * @param source The Window to copy from
+         * @param callbackExecutor The executor to run the callback on
+         * @param listener The callback for when the copy request is completed
+         * @return A {@link Builder} builder to set the optional params & execute the request
+         */
+        public static @NonNull Builder ofWindow(@NonNull Window source,
+                                                @NonNull Executor callbackExecutor,
+                                                @NonNull Consumer<Result> listener) {
+            final Rect insets = new Rect();
+            final Surface surface = sourceForWindow(source, insets);
+            return new Builder(new Request(surface, insets, callbackExecutor, listener));
+        }
+
+        /**
+         * Creates a PixelCopy request for the {@link Window} that the given {@link View} is
+         * attached to.
+         *
+         * Note that this copy request is not cropped to the area the View occupies by default. If
+         * that behavior is desired, use {@link View#getLocationInWindow(int[])} combined with
+         * {@link Builder#setSourceRect(Rect)} to set a crop area to restrict the copy operation.
+         *
+         * @param source A View that {@link View#isAttachedToWindow() is attached} to a window that
+         *               will be used to retrieve the window to copy from.
+         * @param callbackExecutor The executor to run the callback on
+         * @param listener The callback for when the copy request is completed
+         * @return A {@link Builder} builder to set the optional params & execute the request
+         */
+        public static @NonNull Builder ofWindow(@NonNull View source,
+                                                @NonNull Executor callbackExecutor,
+                                                @NonNull Consumer<Result> listener) {
+            if (source == null || !source.isAttachedToWindow()) {
+                throw new IllegalArgumentException(
+                        "View must not be null & must be attached to window");
+            }
+            final Rect insets = new Rect();
+            Surface surface = null;
+            final ViewRootImpl root = source.getViewRootImpl();
+            if (root != null) {
+                surface = root.mSurface;
+                insets.set(root.mWindowAttributes.surfaceInsets);
+            }
+            if (surface == null || !surface.isValid()) {
+                throw new IllegalArgumentException(
+                        "Window doesn't have a backing surface!");
+            }
+            return new Builder(new Request(surface, insets, callbackExecutor, listener));
+        }
+
+        /**
+         * Creates a PixelCopy request for the given {@link Surface}
+         *
+         * @param source The Surface to copy from. Must be {@link Surface#isValid() valid}.
+         * @param callbackExecutor The executor to run the callback on
+         * @param listener The callback for when the copy request is completed
+         * @return A {@link Builder} builder to set the optional params & execute the request
+         */
+        public static @NonNull Builder ofSurface(@NonNull Surface source,
+                                                 @NonNull Executor callbackExecutor,
+                                                 @NonNull Consumer<Result> listener) {
+            if (source == null || !source.isValid()) {
+                throw new IllegalArgumentException("Source must not be null & must be valid");
+            }
+            return new Builder(new Request(source, null, callbackExecutor, listener));
+        }
+
+        /**
+         * Creates a PixelCopy request for the {@link Surface} belonging to the
+         * given {@link SurfaceView}
+         *
+         * @param source The SurfaceView to copy from. The backing surface must be
+         *               {@link Surface#isValid() valid}
+         * @param callbackExecutor The executor to run the callback on
+         * @param listener The callback for when the copy request is completed
+         * @return A {@link Builder} builder to set the optional params & execute the request
+         */
+        public static @NonNull Builder ofSurface(@NonNull SurfaceView source,
+                                                 @NonNull Executor callbackExecutor,
+                                                 @NonNull Consumer<Result> listener) {
+            return ofSurface(source.getHolder().getSurface(), callbackExecutor, listener);
+        }
+
+        /**
+         * @return The destination bitmap as set by {@link Builder#setDestinationBitmap(Bitmap)}
+         */
+        public @Nullable Bitmap getDestinationBitmap() {
+            return mDest;
+        }
+
+        /**
+         * @return The source rect to copy from as set by {@link Builder#setSourceRect(Rect)}
+         */
+        public @Nullable Rect getSourceRect() {
+            return mSrcRect;
+        }
+
+        /**
+         * @hide
          */
         public void request() {
             if (!mSource.isValid()) {
                 mListenerThread.execute(() -> mListener.accept(
-                        new CopyResult(ERROR_SOURCE_INVALID, null)));
+                        new Result(ERROR_SOURCE_INVALID, null)));
                 return;
             }
             HardwareRenderer.copySurfaceInto(mSource, new HardwareRenderer.CopyRequest(
@@ -416,93 +549,18 @@
                 @Override
                 public void onCopyFinished(int result) {
                     mListenerThread.execute(() -> mListener.accept(
-                            new CopyResult(result, mDestinationBitmap)));
+                            new Result(result, mDestinationBitmap)));
                 }
             });
         }
     }
 
     /**
-     * Creates a PixelCopy request for the given {@link Window}
-     * @param source The Window to copy from
-     * @param callbackExecutor The executor to run the callback on
-     * @param listener The callback for when the copy request is completed
-     * @return A {@link Request} builder to set the optional params & execute the request
+     * Executes the pixel copy request
+     * @param request The request to execute
      */
-    public static @NonNull Request ofWindow(@NonNull Window source,
-                                            @NonNull Executor callbackExecutor,
-                                            @NonNull Consumer<CopyResult> listener) {
-        final Rect insets = new Rect();
-        final Surface surface = sourceForWindow(source, insets);
-        return new Request(surface, insets, callbackExecutor, listener);
-    }
-
-    /**
-     * Creates a PixelCopy request for the {@link Window} that the given {@link View} is
-     * attached to.
-     *
-     * Note that this copy request is not cropped to the area the View occupies by default. If that
-     * behavior is desired, use {@link View#getLocationInWindow(int[])} combined with
-     * {@link Request#setSourceRect(Rect)} to set a crop area to restrict the copy operation.
-     *
-     * @param source A View that {@link View#isAttachedToWindow() is attached} to a window that
-     *               will be used to retrieve the window to copy from.
-     * @param callbackExecutor The executor to run the callback on
-     * @param listener The callback for when the copy request is completed
-     * @return A {@link Request} builder to set the optional params & execute the request
-     */
-    public static @NonNull Request ofWindow(@NonNull View source,
-                                            @NonNull Executor callbackExecutor,
-                                            @NonNull Consumer<CopyResult> listener) {
-        if (source == null || !source.isAttachedToWindow()) {
-            throw new IllegalArgumentException(
-                    "View must not be null & must be attached to window");
-        }
-        final Rect insets = new Rect();
-        Surface surface = null;
-        final ViewRootImpl root = source.getViewRootImpl();
-        if (root != null) {
-            surface = root.mSurface;
-            insets.set(root.mWindowAttributes.surfaceInsets);
-        }
-        if (surface == null || !surface.isValid()) {
-            throw new IllegalArgumentException(
-                    "Window doesn't have a backing surface!");
-        }
-        return new Request(surface, insets, callbackExecutor, listener);
-    }
-
-    /**
-     * Creates a PixelCopy request for the given {@link Surface}
-     *
-     * @param source The Surface to copy from. Must be {@link Surface#isValid() valid}.
-     * @param callbackExecutor The executor to run the callback on
-     * @param listener The callback for when the copy request is completed
-     * @return A {@link Request} builder to set the optional params & execute the request
-     */
-    public static @NonNull Request ofSurface(@NonNull Surface source,
-                                             @NonNull Executor callbackExecutor,
-                                             @NonNull Consumer<CopyResult> listener) {
-        if (source == null || !source.isValid()) {
-            throw new IllegalArgumentException("Source must not be null & must be valid");
-        }
-        return new Request(source, null, callbackExecutor, listener);
-    }
-
-    /**
-     * Creates a PixelCopy request for the {@link Surface} belonging to the
-     * given {@link SurfaceView}
-     *
-     * @param source The SurfaceView to copy from. The backing surface must be
-     *               {@link Surface#isValid() valid}
-     * @param callbackExecutor The executor to run the callback on
-     * @param listener The callback for when the copy request is completed
-     * @return A {@link Request} builder to set the optional params & execute the request
-     */
-    public static @NonNull Request ofSurface(@NonNull SurfaceView source,
-                                             @NonNull Executor callbackExecutor,
-                                             @NonNull Consumer<CopyResult> listener) {
-        return ofSurface(source.getHolder().getSurface(), callbackExecutor, listener);
+    public static void request(@NonNull Request request) {
+        request.request();
     }
 
     private PixelCopy() {}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 1d513e4..16760e26 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -1873,6 +1873,11 @@
         @Override
         public void onActivityPreCreated(@NonNull Activity activity,
                 @Nullable Bundle savedInstanceState) {
+            if (activity.isChild()) {
+                // Skip Activity that is child of another Activity (ActivityGroup) because it's
+                // window will just be a child of the parent Activity window.
+                return;
+            }
             synchronized (mLock) {
                 final IBinder activityToken = activity.getActivityToken();
                 final IBinder initialTaskFragmentToken =
@@ -1904,6 +1909,11 @@
         @Override
         public void onActivityPostCreated(@NonNull Activity activity,
                 @Nullable Bundle savedInstanceState) {
+            if (activity.isChild()) {
+                // Skip Activity that is child of another Activity (ActivityGroup) because it's
+                // window will just be a child of the parent Activity window.
+                return;
+            }
             // Calling after Activity#onCreate is complete to allow the app launch something
             // first. In case of a configured placeholder activity we want to make sure
             // that we don't launch it if an activity itself already requested something to be
@@ -1921,6 +1931,11 @@
 
         @Override
         public void onActivityConfigurationChanged(@NonNull Activity activity) {
+            if (activity.isChild()) {
+                // Skip Activity that is child of another Activity (ActivityGroup) because it's
+                // window will just be a child of the parent Activity window.
+                return;
+            }
             synchronized (mLock) {
                 final TransactionRecord transactionRecord = mTransactionManager
                         .startNewTransaction();
@@ -1934,6 +1949,11 @@
 
         @Override
         public void onActivityPostDestroyed(@NonNull Activity activity) {
+            if (activity.isChild()) {
+                // Skip Activity that is child of another Activity (ActivityGroup) because it's
+                // window will just be a child of the parent Activity window.
+                return;
+            }
             synchronized (mLock) {
                 SplitController.this.onActivityDestroyed(activity);
             }
@@ -1969,7 +1989,11 @@
             if (who instanceof Activity) {
                 // We will check if the new activity should be split with the activity that launched
                 // it.
-                launchingActivity = (Activity) who;
+                final Activity activity = (Activity) who;
+                // For Activity that is child of another Activity (ActivityGroup), treat the parent
+                // Activity as the launching one because it's window will just be a child of the
+                // parent Activity window.
+                launchingActivity = activity.isChild() ? activity.getParent() : activity;
                 if (isInPictureInPicture(launchingActivity)) {
                     // We don't embed activity when it is in PIP.
                     return super.onStartActivity(who, intent, options);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 7960323..362f1fa 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -227,7 +227,7 @@
         final TaskFragmentContainer curSecondaryContainer = mController.getContainerWithActivity(
                 secondaryActivity);
         TaskFragmentContainer containerToAvoid = primaryContainer;
-        if (curSecondaryContainer != null
+        if (curSecondaryContainer != null && curSecondaryContainer != primaryContainer
                 && (rule.shouldClearTop() || primaryContainer.isAbove(curSecondaryContainer))) {
             // Do not reuse the current TaskFragment if the rule is to clear top, or if it is below
             // the primary TaskFragment.
diff --git a/libs/WindowManager/Shell/res/drawable/caption_desktop_button.xml b/libs/WindowManager/Shell/res/drawable/caption_desktop_button.xml
new file mode 100644
index 0000000..8779cc0
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/caption_desktop_button.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="32.0dp"
+        android:height="32.0dp"
+        android:viewportWidth="32.0"
+        android:viewportHeight="32.0"
+>
+    <group android:scaleX="0.5"
+           android:scaleY="0.5"
+           android:translateX="6.0"
+           android:translateY="6.0">
+        <path
+            android:fillColor="@android:color/black"
+            android:pathData="M5.958,37.708Q4.458,37.708 3.354,36.604Q2.25,35.5 2.25,34V18.292Q2.25,16.792 3.354,15.688Q4.458,14.583 5.958,14.583H9.5V5.958Q9.5,4.458 10.625,3.354Q11.75,2.25 13.208,2.25H34Q35.542,2.25 36.646,3.354Q37.75,4.458 37.75,5.958V21.667Q37.75,23.167 36.646,24.271Q35.542,25.375 34,25.375H30.5V34Q30.5,35.5 29.396,36.604Q28.292,37.708 26.792,37.708ZM5.958,34H26.792Q26.792,34 26.792,34Q26.792,34 26.792,34V21.542H5.958V34Q5.958,34 5.958,34Q5.958,34 5.958,34ZM30.5,21.667H34Q34,21.667 34,21.667Q34,21.667 34,21.667V9.208H13.208V14.583H26.833Q28.375,14.583 29.438,15.667Q30.5,16.75 30.5,18.25Z"/>
+    </group>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/caption_floating_button.xml b/libs/WindowManager/Shell/res/drawable/caption_floating_button.xml
new file mode 100644
index 0000000..ea0fbb0
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/caption_floating_button.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="32.0dp"
+        android:height="32.0dp"
+        android:viewportWidth="32.0"
+        android:viewportHeight="32.0"
+>
+    <group android:scaleX="0.5"
+           android:scaleY="0.5"
+           android:translateX="6.0"
+           android:translateY="6.0">
+        <path
+            android:fillColor="@android:color/black"
+            android:pathData="M18.167,21.875H29.833V10.208H18.167ZM7.875,35.833Q6.375,35.833 5.271,34.729Q4.167,33.625 4.167,32.125V7.875Q4.167,6.375 5.271,5.271Q6.375,4.167 7.875,4.167H32.125Q33.625,4.167 34.729,5.271Q35.833,6.375 35.833,7.875V32.125Q35.833,33.625 34.729,34.729Q33.625,35.833 32.125,35.833ZM7.875,32.125H32.125Q32.125,32.125 32.125,32.125Q32.125,32.125 32.125,32.125V7.875Q32.125,7.875 32.125,7.875Q32.125,7.875 32.125,7.875H7.875Q7.875,7.875 7.875,7.875Q7.875,7.875 7.875,7.875V32.125Q7.875,32.125 7.875,32.125Q7.875,32.125 7.875,32.125ZM7.875,7.875Q7.875,7.875 7.875,7.875Q7.875,7.875 7.875,7.875V32.125Q7.875,32.125 7.875,32.125Q7.875,32.125 7.875,32.125Q7.875,32.125 7.875,32.125Q7.875,32.125 7.875,32.125V7.875Q7.875,7.875 7.875,7.875Q7.875,7.875 7.875,7.875Z"/>
+    </group>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/caption_fullscreen_button.xml b/libs/WindowManager/Shell/res/drawable/caption_fullscreen_button.xml
new file mode 100644
index 0000000..c55cbe2
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/caption_fullscreen_button.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="32.0dp"
+        android:height="32.0dp"
+        android:viewportWidth="32.0"
+        android:viewportHeight="32.0"
+>
+    <group android:scaleX="0.5"
+           android:scaleY="0.5"
+           android:translateX="6.0"
+           android:translateY="6.0">
+        <path
+            android:fillColor="@android:color/black"
+            android:pathData="M34.042,14.625V9.333Q34.042,9.333 34.042,9.333Q34.042,9.333 34.042,9.333H28.708V5.708H33.917Q35.458,5.708 36.562,6.833Q37.667,7.958 37.667,9.458V14.625ZM2.375,14.625V9.458Q2.375,7.958 3.479,6.833Q4.583,5.708 6.125,5.708H11.292V9.333H6Q6,9.333 6,9.333Q6,9.333 6,9.333V14.625ZM28.708,34.25V30.667H34.042Q34.042,30.667 34.042,30.667Q34.042,30.667 34.042,30.667V25.333H37.667V30.542Q37.667,32 36.562,33.125Q35.458,34.25 33.917,34.25ZM6.125,34.25Q4.583,34.25 3.479,33.125Q2.375,32 2.375,30.542V25.333H6V30.667Q6,30.667 6,30.667Q6,30.667 6,30.667H11.292V34.25ZM9.333,27.292V12.667H30.708V27.292ZM12.917,23.708H27.125V16.25H12.917ZM12.917,23.708V16.25V23.708Z"/>
+    </group>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/caption_more_button.xml b/libs/WindowManager/Shell/res/drawable/caption_more_button.xml
new file mode 100644
index 0000000..447df43
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/caption_more_button.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="32.0dp"
+        android:height="32.0dp"
+        android:viewportWidth="32.0"
+        android:viewportHeight="32.0"
+>
+    <group android:scaleX="0.5"
+           android:scaleY="0.5"
+           android:translateX="6.0"
+           android:translateY="6.0">
+        <path
+            android:fillColor="@android:color/black"
+            android:pathData="M8.083,22.833Q6.917,22.833 6.104,22Q5.292,21.167 5.292,20Q5.292,18.833 6.125,18Q6.958,17.167 8.125,17.167Q9.292,17.167 10.125,18Q10.958,18.833 10.958,20Q10.958,21.167 10.125,22Q9.292,22.833 8.083,22.833ZM20,22.833Q18.833,22.833 18,22Q17.167,21.167 17.167,20Q17.167,18.833 18,18Q18.833,17.167 20,17.167Q21.167,17.167 22,18Q22.833,18.833 22.833,20Q22.833,21.167 22,22Q21.167,22.833 20,22.833ZM31.875,22.833Q30.708,22.833 29.875,22Q29.042,21.167 29.042,20Q29.042,18.833 29.875,18Q30.708,17.167 31.917,17.167Q33.083,17.167 33.896,18Q34.708,18.833 34.708,20Q34.708,21.167 33.875,22Q33.042,22.833 31.875,22.833Z"/>
+    </group>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/caption_split_screen_button.xml b/libs/WindowManager/Shell/res/drawable/caption_split_screen_button.xml
new file mode 100644
index 0000000..c334a54
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/caption_split_screen_button.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="32.0dp"
+        android:height="32.0dp"
+        android:viewportWidth="32.0"
+        android:viewportHeight="32.0"
+>
+    <group android:translateX="6.0"
+           android:translateY="8.0">
+        <path
+            android:fillColor="@android:color/black"
+            android:pathData="M18 14L13 14L13 2L18 2L18 14ZM20 14L20 2C20 0.9 19.1 -3.93402e-08 18 -8.74228e-08L13 -3.0598e-07C11.9 -3.54062e-07 11 0.9 11 2L11 14C11 15.1 11.9 16 13 16L18 16C19.1 16 20 15.1 20 14ZM7 14L2 14L2 2L7 2L7 14ZM9 14L9 2C9 0.9 8.1 -5.20166e-07 7 -5.68248e-07L2 -7.86805e-07C0.9 -8.34888e-07 -3.93403e-08 0.9 -8.74228e-08 2L-6.11959e-07 14C-6.60042e-07 15.1 0.9 16 2 16L7 16C8.1 16 9 15.1 9 14Z"/>    </group>
+</vector>
diff --git a/libs/WindowManager/Shell/res/drawable/handle_menu_background.xml b/libs/WindowManager/Shell/res/drawable/handle_menu_background.xml
new file mode 100644
index 0000000..e307f00
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/handle_menu_background.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="210.0dp"
+        android:height="64.0dp"
+        android:tint="@color/decor_button_light_color"
+>
+    <group android:scaleX="0.5"
+           android:scaleY="0.5"
+           android:translateX="8.0"
+           android:translateY="8.0" >
+        <path
+            android:fillColor="@android:color/white"
+            android:pathData="M18.3334 14L13.3334 14L13.3334 2L18.3334 2L18.3334 14ZM20.3334 14L20.3334 2C20.3334 0.9 19.4334 -3.93402e-08 18.3334 -8.74228e-08L13.3334 -3.0598e-07C12.2334 -3.54062e-07 11.3334 0.9 11.3334 2L11.3334 14C11.3334 15.1 12.2334 16 13.3334 16L18.3334 16C19.4334 16 20.3334 15.1 20.3334 14ZM7.33337 14L2.33337 14L2.33337 2L7.33337 2L7.33337 14ZM9.33337 14L9.33337 2C9.33337 0.899999 8.43337 -5.20166e-07 7.33337 -5.68248e-07L2.33337 -7.86805e-07C1.23337 -8.34888e-07 0.333374 0.899999 0.333374 2L0.333373 14C0.333373 15.1 1.23337 16 2.33337 16L7.33337 16C8.43337 16 9.33337 15.1 9.33337 14Z"/>
+    </group>
+</vector>
diff --git a/libs/WindowManager/Shell/res/layout/caption_handle_menu.xml b/libs/WindowManager/Shell/res/layout/caption_handle_menu.xml
new file mode 100644
index 0000000..d9a140b
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/caption_handle_menu.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+    <!--
+      ~ Copyright (C) 2022 The Android Open Source Project
+      ~
+      ~ Licensed under the Apache License, Version 2.0 (the "License");
+      ~ you may not use this file except in compliance with the License.
+      ~ You may obtain a copy of the License at
+      ~
+      ~      http://www.apache.org/licenses/LICENSE-2.0
+      ~
+      ~ Unless required by applicable law or agreed to in writing, software
+      ~ distributed under the License is distributed on an "AS IS" BASIS,
+      ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+      ~ See the License for the specific language governing permissions and
+      ~ limitations under the License.
+      -->
+<com.android.wm.shell.windowdecor.WindowDecorLinearLayout
+xmlns:android="http://schemas.android.com/apk/res/android"
+android:id="@+id/handle_menu"
+android:layout_width="wrap_content"
+android:layout_height="wrap_content"
+android:gravity="center_horizontal"
+android:background="@drawable/decor_caption_title">
+    <Button
+        style="@style/CaptionButtonStyle"
+        android:id="@+id/fullscreen_button"
+        android:contentDescription="@string/fullscreen_text"
+        android:background="@drawable/caption_fullscreen_button"/>
+    <Button
+        style="@style/CaptionButtonStyle"
+        android:id="@+id/split_screen_button"
+        android:contentDescription="@string/split_screen_text"
+        android:background="@drawable/caption_split_screen_button"/>
+    <Button
+        style="@style/CaptionButtonStyle"
+        android:id="@+id/floating_button"
+        android:contentDescription="@string/float_button_text"
+        android:background="@drawable/caption_floating_button"/>
+    <Button
+        style="@style/CaptionButtonStyle"
+        android:id="@+id/desktop_button"
+        android:contentDescription="@string/desktop_text"
+        android:background="@drawable/caption_desktop_button"/>
+    <Button
+        style="@style/CaptionButtonStyle"
+        android:id="@+id/more_button"
+        android:contentDescription="@string/more_button_text"
+        android:background="@drawable/caption_more_button"/>
+</com.android.wm.shell.windowdecor.WindowDecorLinearLayout>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml b/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml
index 38cd570..51e634c 100644
--- a/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml
+++ b/libs/WindowManager/Shell/res/layout/caption_window_decoration.xml
@@ -19,14 +19,10 @@
     android:id="@+id/caption"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
-    android:gravity="center_horizontal"
     android:background="@drawable/decor_caption_title">
     <Button
+        style="@style/CaptionButtonStyle"
         android:id="@+id/back_button"
-        android:layout_width="32dp"
-        android:layout_height="32dp"
-        android:layout_margin="5dp"
-        android:padding="4dp"
         android:contentDescription="@string/back_button_text"
         android:background="@drawable/decor_back_button_dark"
     />
@@ -39,11 +35,8 @@
         android:contentDescription="@string/handle_text"
         android:background="@drawable/decor_handle_dark"/>
     <Button
+        style="@style/CaptionButtonStyle"
         android:id="@+id/close_window"
-        android:layout_width="32dp"
-        android:layout_height="32dp"
-        android:layout_margin="5dp"
-        android:padding="4dp"
         android:contentDescription="@string/close_button_text"
         android:background="@drawable/decor_close_button_dark"/>
 </com.android.wm.shell.windowdecor.WindowDecorLinearLayout>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 4807f08..097a567 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -202,4 +202,14 @@
     <string name="back_button_text">Back</string>
     <!-- Accessibility text for the caption handle [CHAR LIMIT=NONE] -->
     <string name="handle_text">Handle</string>
+    <!-- Accessibility text for the handle fullscreen button [CHAR LIMIT=NONE] -->
+    <string name="fullscreen_text">Fullscreen</string>
+    <!-- Accessibility text for the handle desktop button [CHAR LIMIT=NONE] -->
+    <string name="desktop_text">Desktop Mode</string>
+    <!-- Accessibility text for the handle split screen button [CHAR LIMIT=NONE] -->
+    <string name="split_screen_text">Split Screen</string>
+    <!-- Accessibility text for the handle more options button [CHAR LIMIT=NONE] -->
+    <string name="more_button_text">More</string>
+    <!-- Accessibility text for the handle floating window button [CHAR LIMIT=NONE] -->
+    <string name="float_button_text">Float</string>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values/styles.xml b/libs/WindowManager/Shell/res/values/styles.xml
index 19f7c3e..a859721 100644
--- a/libs/WindowManager/Shell/res/values/styles.xml
+++ b/libs/WindowManager/Shell/res/values/styles.xml
@@ -30,6 +30,13 @@
         <item name="android:activityCloseExitAnimation">@anim/forced_resizable_exit</item>
     </style>
 
+    <style name="CaptionButtonStyle">
+        <item name="android:layout_width">32dp</item>
+        <item name="android:layout_height">32dp</item>
+        <item name="android:layout_margin">5dp</item>
+        <item name="android:padding">4dp</item>
+    </style>
+
     <style name="DockedDividerBackground">
         <item name="android:layout_width">match_parent</item>
         <item name="android:layout_height">@dimen/split_divider_bar_width</item>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index dc5b9a1e..64220c8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -38,6 +38,7 @@
 import android.provider.Settings.Global;
 import android.util.Log;
 import android.util.SparseArray;
+import android.view.IRemoteAnimationFinishedCallback;
 import android.view.IRemoteAnimationRunner;
 import android.view.IWindowFocusObserver;
 import android.view.InputDevice;
@@ -187,6 +188,31 @@
         }
         mShellController.addExternalInterface(KEY_EXTRA_SHELL_BACK_ANIMATION,
                 this::createExternalInterface, this);
+
+        initBackAnimationRunners();
+    }
+
+    private void initBackAnimationRunners() {
+        final IOnBackInvokedCallback dummyCallback = new IOnBackInvokedCallback.Default();
+        final IRemoteAnimationRunner dummyRunner = new IRemoteAnimationRunner.Default() {
+            @Override
+            public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
+                    RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
+                    IRemoteAnimationFinishedCallback finishedCallback) throws RemoteException {
+                // Animation missing. Simply finish animation.
+                finishedCallback.onAnimationFinished();
+            }
+        };
+
+        final BackAnimationRunner dummyBackRunner =
+                new BackAnimationRunner(dummyCallback, dummyRunner);
+        final CrossTaskBackAnimation crossTaskAnimation = new CrossTaskBackAnimation(mContext);
+        mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_TASK,
+                new BackAnimationRunner(crossTaskAnimation.mCallback, crossTaskAnimation.mRunner));
+        // TODO (238474994): register cross activity animation when it's completed.
+        mAnimationDefinition.set(BackNavigationInfo.TYPE_CROSS_ACTIVITY, dummyBackRunner);
+        // TODO (236760237): register dialog close animation when it's completed.
+        mAnimationDefinition.set(BackNavigationInfo.TYPE_DIALOG_CLOSE, dummyBackRunner);
     }
 
     private void setupAnimationDeveloperSettingsObserver(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
new file mode 100644
index 0000000..2074b6a
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
@@ -0,0 +1,365 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.back;
+
+import static android.view.RemoteAnimationTarget.MODE_CLOSING;
+import static android.view.RemoteAnimationTarget.MODE_OPENING;
+import static android.window.BackEvent.EDGE_RIGHT;
+
+import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
+
+import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.ValueAnimator;
+import android.annotation.NonNull;
+import android.content.Context;
+import android.graphics.Matrix;
+import android.graphics.PointF;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.os.RemoteException;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.IRemoteAnimationRunner;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.window.BackEvent;
+import android.window.BackProgressAnimator;
+import android.window.IOnBackInvokedCallback;
+
+import com.android.internal.policy.ScreenDecorationsUtils;
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.annotations.ShellMainThread;
+
+/**
+ * Controls the animation of swiping back and returning to another task.
+ *
+ * This is a two part animation. The first part is an animation that tracks gesture location to
+ * scale and move the closing and entering app windows.
+ * Once the gesture is committed, the second part remains the closing window in place.
+ * The entering window plays the rest of app opening transition to enter full screen.
+ *
+ * This animation is used only for apps that enable back dispatching via
+ * {@link android.window.OnBackInvokedDispatcher}. The controller registers
+ * an {@link IOnBackInvokedCallback} with WM Shell and receives back dispatches when a back
+ * navigation to launcher starts.
+ */
+@ShellMainThread
+class CrossTaskBackAnimation {
+    private static final float[] BACKGROUNDCOLOR = {0.263f, 0.263f, 0.227f};
+
+    /**
+     * Minimum scale of the entering window.
+     */
+    private static final float ENTERING_MIN_WINDOW_SCALE = 0.85f;
+
+    /**
+     * Minimum scale of the closing window.
+     */
+    private static final float CLOSING_MIN_WINDOW_SCALE = 0.75f;
+
+    /**
+     * Minimum color scale of the closing window.
+     */
+    private static final float CLOSING_MIN_WINDOW_COLOR_SCALE = 0.1f;
+
+    /**
+     * The margin between the entering window and the closing window
+     */
+    private static final int WINDOW_MARGIN = 35;
+
+    /** Max window translation in the Y axis. */
+    private static final int WINDOW_MAX_DELTA_Y = 160;
+
+    private final Rect mStartTaskRect = new Rect();
+    private final float mCornerRadius;
+
+    // The closing window properties.
+    private final RectF mClosingCurrentRect = new RectF();
+
+    // The entering window properties.
+    private final Rect mEnteringStartRect = new Rect();
+    private final RectF mEnteringCurrentRect = new RectF();
+
+    private final PointF mInitialTouchPos = new PointF();
+    private final Interpolator mInterpolator = new AccelerateDecelerateInterpolator();
+
+    private final Matrix mTransformMatrix = new Matrix();
+
+    private final float[] mTmpFloat9 = new float[9];
+    private final float[] mTmpTranslate = {0, 0, 0};
+
+    private RemoteAnimationTarget mEnteringTarget;
+    private RemoteAnimationTarget mClosingTarget;
+    private SurfaceControl mBackgroundSurface;
+    private SurfaceControl.Transaction mTransaction = new SurfaceControl.Transaction();
+
+    private boolean mBackInProgress = false;
+
+    private boolean mIsRightEdge;
+    private float mProgress = 0;
+    private PointF mTouchPos = new PointF();
+    private IRemoteAnimationFinishedCallback mFinishCallback;
+
+    private BackProgressAnimator mProgressAnimator = new BackProgressAnimator();
+
+    final IOnBackInvokedCallback mCallback = new IOnBackInvokedCallback.Default() {
+        @Override
+        public void onBackStarted(BackEvent backEvent) {
+            mProgressAnimator.onBackStarted(backEvent,
+                    CrossTaskBackAnimation.this::onGestureProgress);
+        }
+
+        @Override
+        public void onBackProgressed(@NonNull BackEvent backEvent) {
+            mProgressAnimator.onBackProgressed(backEvent);
+        }
+
+        @Override
+        public void onBackCancelled() {
+            mProgressAnimator.reset();
+            finishAnimation();
+        }
+
+        @Override
+        public void onBackInvoked() {
+            mProgressAnimator.reset();
+            onGestureCommitted();
+        }
+    };
+
+    final IRemoteAnimationRunner mRunner = new IRemoteAnimationRunner.Default() {
+        @Override
+        public void onAnimationStart(int transit, RemoteAnimationTarget[] apps,
+                RemoteAnimationTarget[] wallpapers, RemoteAnimationTarget[] nonApps,
+                IRemoteAnimationFinishedCallback finishedCallback) {
+            ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Start back to task animation.");
+            for (RemoteAnimationTarget a : apps) {
+                if (a.mode == MODE_CLOSING) {
+                    mClosingTarget = a;
+                }
+                if (a.mode == MODE_OPENING) {
+                    mEnteringTarget = a;
+                }
+            }
+
+            startBackAnimation();
+            mFinishCallback = finishedCallback;
+        }
+    };
+
+    CrossTaskBackAnimation(Context context) {
+        mCornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context);
+    }
+
+    private float getInterpolatedProgress(float backProgress) {
+        return 1 - (1 - backProgress) * (1 - backProgress) * (1 - backProgress);
+    }
+
+    private void startBackAnimation() {
+        if (mEnteringTarget == null || mClosingTarget == null) {
+            ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Entering target or closing target is null.");
+            return;
+        }
+
+        // Offset start rectangle to align task bounds.
+        mStartTaskRect.set(mClosingTarget.windowConfiguration.getBounds());
+        mStartTaskRect.offsetTo(0, 0);
+
+        // Draw background.
+        mBackgroundSurface = new SurfaceControl.Builder()
+                .setName("Background of Back Navigation")
+                .setColorLayer()
+                .setHidden(false)
+                .build();
+        mTransaction.setColor(mBackgroundSurface, BACKGROUNDCOLOR)
+                .setLayer(mBackgroundSurface, -1);
+        mTransaction.apply();
+    }
+
+    private void updateGestureBackProgress(float progress, BackEvent event) {
+        if (mEnteringTarget == null || mClosingTarget == null) {
+            return;
+        }
+
+        float touchX = event.getTouchX();
+        float touchY = event.getTouchY();
+        float dX = Math.abs(touchX - mInitialTouchPos.x);
+
+        // The 'follow width' is the width of the window if it completely matches
+        // the gesture displacement.
+        final int width = mStartTaskRect.width();
+        final int height = mStartTaskRect.height();
+
+        // The 'progress width' is the width of the window if it strictly linearly interpolates
+        // to minimum scale base on progress.
+        float enteringScale = mapRange(progress, 1, ENTERING_MIN_WINDOW_SCALE);
+        float closingScale = mapRange(progress, 1, CLOSING_MIN_WINDOW_SCALE);
+        float closingColorScale = mapRange(progress, 1, CLOSING_MIN_WINDOW_COLOR_SCALE);
+
+        // The final width is derived from interpolating between the follow with and progress width
+        // using gesture progress.
+        float enteringWidth = enteringScale * width;
+        float closingWidth = closingScale * width;
+        float enteringHeight = (float) height / width * enteringWidth;
+        float closingHeight = (float) height / width * closingWidth;
+
+        float deltaYRatio = (touchY - mInitialTouchPos.y) / height;
+        // Base the window movement in the Y axis on the touch movement in the Y axis.
+        float deltaY = (float) Math.sin(deltaYRatio * Math.PI * 0.5f) * WINDOW_MAX_DELTA_Y;
+        // Move the window along the Y axis.
+        float closingTop = (height - closingHeight) * 0.5f + deltaY;
+        float enteringTop = (height - enteringHeight) * 0.5f + deltaY;
+        // Move the window along the X axis.
+        float right = width - (progress * WINDOW_MARGIN);
+        float left = right - closingWidth;
+
+        mClosingCurrentRect.set(left, closingTop, right, closingTop + closingHeight);
+        mEnteringCurrentRect.set(left - enteringWidth - WINDOW_MARGIN, enteringTop,
+                left - WINDOW_MARGIN, enteringTop + enteringHeight);
+
+        applyTransform(mClosingTarget.leash, mClosingCurrentRect, mCornerRadius);
+        applyColorTransform(mClosingTarget.leash, closingColorScale);
+        applyTransform(mEnteringTarget.leash, mEnteringCurrentRect, mCornerRadius);
+        mTransaction.apply();
+    }
+
+    private void updatePostCommitClosingAnimation(float progress) {
+        mTransaction.setLayer(mClosingTarget.leash, 0);
+        float alpha = mapRange(progress, 1, 0);
+        mTransaction.setAlpha(mClosingTarget.leash, alpha);
+    }
+
+    private void updatePostCommitEnteringAnimation(float progress) {
+        float left = mapRange(progress, mEnteringStartRect.left, mStartTaskRect.left);
+        float top = mapRange(progress, mEnteringStartRect.top, mStartTaskRect.top);
+        float width = mapRange(progress, mEnteringStartRect.width(), mStartTaskRect.width());
+        float height = mapRange(progress, mEnteringStartRect.height(), mStartTaskRect.height());
+
+        mEnteringCurrentRect.set(left, top, left + width, top + height);
+        applyTransform(mEnteringTarget.leash, mEnteringCurrentRect, mCornerRadius);
+    }
+
+    /** Transform the target window to match the target rect. */
+    private void applyTransform(SurfaceControl leash, RectF targetRect, float cornerRadius) {
+        if (leash == null) {
+            return;
+        }
+
+        final float scale = targetRect.width() / mStartTaskRect.width();
+        mTransformMatrix.reset();
+        mTransformMatrix.setScale(scale, scale);
+        mTransformMatrix.postTranslate(targetRect.left, targetRect.top);
+        mTransaction.setMatrix(leash, mTransformMatrix, mTmpFloat9)
+                .setWindowCrop(leash, mStartTaskRect)
+                .setCornerRadius(leash, cornerRadius);
+    }
+
+    private void applyColorTransform(SurfaceControl leash, float colorScale) {
+        if (leash == null) {
+            return;
+        }
+        computeScaleTransformMatrix(colorScale, mTmpFloat9);
+        mTransaction.setColorTransform(leash, mTmpFloat9, mTmpTranslate);
+    }
+
+    static void computeScaleTransformMatrix(float scale, float[] matrix) {
+        matrix[0] = scale;
+        matrix[1] = 0;
+        matrix[2] = 0;
+        matrix[3] = 0;
+        matrix[4] = scale;
+        matrix[5] = 0;
+        matrix[6] = 0;
+        matrix[7] = 0;
+        matrix[8] = scale;
+    }
+
+    private void finishAnimation() {
+        if (mEnteringTarget != null) {
+            mEnteringTarget.leash.release();
+            mEnteringTarget = null;
+        }
+        if (mClosingTarget != null) {
+            mClosingTarget.leash.release();
+            mClosingTarget = null;
+        }
+
+        if (mBackgroundSurface != null) {
+            mBackgroundSurface.release();
+            mBackgroundSurface = null;
+        }
+
+        mBackInProgress = false;
+        mTransformMatrix.reset();
+        mClosingCurrentRect.setEmpty();
+        mInitialTouchPos.set(0, 0);
+
+        if (mFinishCallback != null) {
+            try {
+                mFinishCallback.onAnimationFinished();
+            } catch (RemoteException e) {
+                e.printStackTrace();
+            }
+            mFinishCallback = null;
+        }
+    }
+
+    private void onGestureProgress(@NonNull BackEvent backEvent) {
+        if (!mBackInProgress) {
+            mInitialTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY());
+            mIsRightEdge = backEvent.getSwipeEdge() == EDGE_RIGHT;
+            mBackInProgress = true;
+        }
+        mProgress = backEvent.getProgress();
+        mTouchPos.set(backEvent.getTouchX(), backEvent.getTouchY());
+        updateGestureBackProgress(getInterpolatedProgress(mProgress), backEvent);
+    }
+
+    private void onGestureCommitted() {
+        if (mEnteringTarget == null || mClosingTarget == null) {
+            finishAnimation();
+            return;
+        }
+
+        // We enter phase 2 of the animation, the starting coordinates for phase 2 are the current
+        // coordinate of the gesture driven phase.
+        mEnteringCurrentRect.round(mEnteringStartRect);
+
+        ValueAnimator valueAnimator = ValueAnimator.ofFloat(1f, 0f).setDuration(300);
+        valueAnimator.setInterpolator(mInterpolator);
+        valueAnimator.addUpdateListener(animation -> {
+            float progress = animation.getAnimatedFraction();
+            updatePostCommitEnteringAnimation(progress);
+            updatePostCommitClosingAnimation(progress);
+            mTransaction.apply();
+        });
+
+        valueAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                finishAnimation();
+            }
+        });
+        valueAnimator.start();
+    }
+
+    private static float mapRange(float value, float min, float max) {
+        return min + (value * (max - min));
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index 0bb76b9..bb7c4134 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -38,10 +38,10 @@
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.animation.Interpolator;
 import android.view.animation.PathInterpolator;
+import android.view.inputmethod.InputMethodManagerGlobal;
 
 import androidx.annotation.VisibleForTesting;
 
-import com.android.internal.inputmethod.IInputMethodManagerGlobal;
 import com.android.wm.shell.sysui.ShellInit;
 
 import java.util.ArrayList;
@@ -515,7 +515,7 @@
     void removeImeSurface() {
         // Remove the IME surface to make the insets invisible for
         // non-client controlled insets.
-        IInputMethodManagerGlobal.removeImeSurface(
+        InputMethodManagerGlobal.removeImeSurface(
                 e -> Slog.e(TAG, "Failed to remove IME surface.", e));
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/OWNERS
new file mode 100644
index 0000000..7237d2b
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/OWNERS
@@ -0,0 +1,2 @@
+# WM shell sub-modules splitscreen owner
+chenghsiuchang@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index c2ad1a9..295a2e3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -69,6 +69,7 @@
 import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
 
 import java.io.PrintWriter;
+import java.util.function.Consumer;
 
 /**
  * Records and handles layout of splits. Helps to calculate proper bounds when configuration or
@@ -85,9 +86,9 @@
     private static final int FLING_ENTER_DURATION = 350;
     private static final int FLING_EXIT_DURATION = 350;
 
-    private final int mDividerWindowWidth;
-    private final int mDividerInsets;
-    private final int mDividerSize;
+    private int mDividerWindowWidth;
+    private int mDividerInsets;
+    private int mDividerSize;
 
     private final Rect mTempRect = new Rect();
     private final Rect mRootBounds = new Rect();
@@ -130,6 +131,7 @@
         mContext = context.createConfigurationContext(configuration);
         mOrientation = configuration.orientation;
         mRotation = configuration.windowConfiguration.getRotation();
+        mDensity = configuration.densityDpi;
         mSplitLayoutHandler = splitLayoutHandler;
         mDisplayImeController = displayImeController;
         mSplitWindowManager = new SplitWindowManager(windowName, mContext, configuration,
@@ -138,24 +140,22 @@
         mImePositionProcessor = new ImePositionProcessor(mContext.getDisplayId());
         mSurfaceEffectPolicy = new ResizingEffectPolicy(parallaxType);
 
-        final Resources resources = context.getResources();
-        mDividerSize = resources.getDimensionPixelSize(R.dimen.split_divider_bar_width);
-        mDividerInsets = getDividerInsets(resources, context.getDisplay());
-        mDividerWindowWidth = mDividerSize + 2 * mDividerInsets;
+        updateDividerConfig(mContext);
 
         mRootBounds.set(configuration.windowConfiguration.getBounds());
         mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds, null);
         resetDividerPosition();
 
-        mDimNonImeSide = resources.getBoolean(R.bool.config_dimNonImeAttachedSide);
+        mDimNonImeSide = mContext.getResources().getBoolean(R.bool.config_dimNonImeAttachedSide);
 
         updateInvisibleRect();
     }
 
-    private int getDividerInsets(Resources resources, Display display) {
+    private void updateDividerConfig(Context context) {
+        final Resources resources = context.getResources();
+        final Display display = context.getDisplay();
         final int dividerInset = resources.getDimensionPixelSize(
                 com.android.internal.R.dimen.docked_stack_divider_insets);
-
         int radius = 0;
         RoundedCorner corner = display.getRoundedCorner(RoundedCorner.POSITION_TOP_LEFT);
         radius = corner != null ? Math.max(radius, corner.getRadius()) : radius;
@@ -166,7 +166,9 @@
         corner = display.getRoundedCorner(RoundedCorner.POSITION_BOTTOM_LEFT);
         radius = corner != null ? Math.max(radius, corner.getRadius()) : radius;
 
-        return Math.max(dividerInset, radius);
+        mDividerInsets = Math.max(dividerInset, radius);
+        mDividerSize = resources.getDimensionPixelSize(R.dimen.split_divider_bar_width);
+        mDividerWindowWidth = mDividerSize + 2 * mDividerInsets;
     }
 
     /** Gets bounds of the primary split with screen based coordinate. */
@@ -308,6 +310,7 @@
         mRotation = rotation;
         mDensity = density;
         mDividerSnapAlgorithm = getSnapAlgorithm(mContext, mRootBounds, null);
+        updateDividerConfig(mContext);
         initDividerPosition(mTempRect);
         updateInvisibleRect();
 
@@ -599,7 +602,7 @@
 
     /** Swich both surface position with animation. */
     public void splitSwitching(SurfaceControl.Transaction t, SurfaceControl leash1,
-            SurfaceControl leash2, Runnable finishCallback) {
+            SurfaceControl leash2, Consumer<Rect> finishCallback) {
         final boolean isLandscape = isLandscape();
         final Rect insets = getDisplayInsets(mContext);
         insets.set(isLandscape ? insets.left : 0, isLandscape ? 0 : insets.top,
@@ -617,18 +620,13 @@
         distBounds1.offset(-mRootBounds.left, -mRootBounds.top);
         distBounds2.offset(-mRootBounds.left, -mRootBounds.top);
         distDividerBounds.offset(-mRootBounds.left, -mRootBounds.top);
-        // DO NOT move to insets area for smooth animation.
-        distBounds1.set(distBounds1.left, distBounds1.top,
-                distBounds1.right - insets.right, distBounds1.bottom - insets.bottom);
-        distBounds2.set(distBounds2.left + insets.left, distBounds2.top + insets.top,
-                distBounds2.right, distBounds2.bottom);
 
         ValueAnimator animator1 = moveSurface(t, leash1, getRefBounds1(), distBounds1,
-                false /* alignStart */);
+                -insets.left, -insets.top);
         ValueAnimator animator2 = moveSurface(t, leash2, getRefBounds2(), distBounds2,
-                true /* alignStart */);
+                insets.left, insets.top);
         ValueAnimator animator3 = moveSurface(t, getDividerLeash(), getRefDividerBounds(),
-                distDividerBounds, true /* alignStart */);
+                distDividerBounds, 0 /* offsetX */, 0 /* offsetY */);
 
         AnimatorSet set = new AnimatorSet();
         set.playTogether(animator1, animator2, animator3);
@@ -638,14 +636,14 @@
             public void onAnimationEnd(Animator animation) {
                 mDividePosition = dividerPos;
                 updateBounds(mDividePosition);
-                finishCallback.run();
+                finishCallback.accept(insets);
             }
         });
         set.start();
     }
 
     private ValueAnimator moveSurface(SurfaceControl.Transaction t, SurfaceControl leash,
-            Rect start, Rect end, boolean alignStart) {
+            Rect start, Rect end, float offsetX, float offsetY) {
         Rect tempStart = new Rect(start);
         Rect tempEnd = new Rect(end);
         final float diffX = tempEnd.left - tempStart.left;
@@ -661,15 +659,15 @@
             final float distY = tempStart.top + scale * diffY;
             final int width = (int) (tempStart.width() + scale * diffWidth);
             final int height = (int) (tempStart.height() + scale * diffHeight);
-            if (alignStart) {
+            if (offsetX == 0 && offsetY == 0) {
                 t.setPosition(leash, distX, distY);
                 t.setWindowCrop(leash, width, height);
             } else {
-                final int offsetX = width - tempStart.width();
-                final int offsetY = height - tempStart.height();
-                t.setPosition(leash, distX + offsetX, distY + offsetY);
+                final int diffOffsetX = (int) (scale * offsetX);
+                final int diffOffsetY = (int) (scale * offsetY);
+                t.setPosition(leash, distX + diffOffsetX, distY + diffOffsetY);
                 mTempRect.set(0, 0, width, height);
-                mTempRect.offsetTo(-offsetX, -offsetY);
+                mTempRect.offsetTo(-diffOffsetX, -diffOffsetY);
                 t.setCrop(leash, mTempRect);
             }
             t.apply();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
index f1670cd..4459f57 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellModule.java
@@ -27,6 +27,7 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.launcher3.icons.IconProvider;
+import com.android.wm.shell.RootDisplayAreaOrganizer;
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.TaskViewTransitions;
@@ -271,11 +272,12 @@
             TaskStackListenerImpl taskStackListener,
             UiEventLogger uiEventLogger,
             InteractionJankMonitor jankMonitor,
+            RootDisplayAreaOrganizer rootDisplayAreaOrganizer,
             @ShellMainThread ShellExecutor mainExecutor,
             @ShellMainThread Handler mainHandler) {
         return OneHandedController.create(context, shellInit, shellCommandHandler, shellController,
                 windowManager, displayController, displayLayout, taskStackListener, jankMonitor,
-                uiEventLogger, mainExecutor, mainHandler);
+                uiEventLogger, rootDisplayAreaOrganizer, mainExecutor, mainHandler);
     }
 
     //
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
index 195ff50..2fafe67 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
@@ -48,7 +48,6 @@
         try {
             int result = Settings.System.getIntForUser(context.getContentResolver(),
                     Settings.System.DESKTOP_MODE, UserHandle.USER_CURRENT);
-            ProtoLog.d(WM_SHELL_DESKTOP_MODE, "isDesktopModeEnabled=%s", result);
             return result != 0;
         } catch (Exception e) {
             ProtoLog.e(WM_SHELL_DESKTOP_MODE, "Failed to read DESKTOP_MODE setting %s", e);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
index c91d54a..b7749fc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
@@ -69,22 +69,28 @@
 
     /**
      * Mark a task with given [taskId] as active.
+     *
+     * @return `true` if the task was not active
      */
-    fun addActiveTask(taskId: Int) {
+    fun addActiveTask(taskId: Int): Boolean {
         val added = activeTasks.add(taskId)
         if (added) {
             activeTasksListeners.onEach { it.onActiveTasksChanged() }
         }
+        return added
     }
 
     /**
      * Remove task with given [taskId] from active tasks.
+     *
+     * @return `true` if the task was active
      */
-    fun removeActiveTask(taskId: Int) {
+    fun removeActiveTask(taskId: Int): Boolean {
         val removed = activeTasks.remove(taskId)
         if (removed) {
             activeTasksListeners.onEach { it.onActiveTasksChanged() }
         }
+        return removed
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
index eaa7158..90b35a5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
@@ -87,11 +87,13 @@
         }
 
         if (DesktopModeStatus.IS_SUPPORTED && taskInfo.isVisible) {
-            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
-                    "Adding active freeform task: #%d", taskInfo.taskId);
-            mDesktopModeTaskRepository.ifPresent(it -> it.addActiveTask(taskInfo.taskId));
-            mDesktopModeTaskRepository.ifPresent(
-                    it -> it.updateVisibleFreeformTasks(taskInfo.taskId, true));
+            mDesktopModeTaskRepository.ifPresent(repository -> {
+                if (repository.addActiveTask(taskInfo.taskId)) {
+                    ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+                            "Adding active freeform task: #%d", taskInfo.taskId);
+                }
+                repository.updateVisibleFreeformTasks(taskInfo.taskId, true);
+            });
         }
     }
 
@@ -102,11 +104,13 @@
         mTasks.remove(taskInfo.taskId);
 
         if (DesktopModeStatus.IS_SUPPORTED) {
-            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
-                    "Removing active freeform task: #%d", taskInfo.taskId);
-            mDesktopModeTaskRepository.ifPresent(it -> it.removeActiveTask(taskInfo.taskId));
-            mDesktopModeTaskRepository.ifPresent(
-                    it -> it.updateVisibleFreeformTasks(taskInfo.taskId, false));
+            mDesktopModeTaskRepository.ifPresent(repository -> {
+                if (repository.removeActiveTask(taskInfo.taskId)) {
+                    ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+                            "Removing active freeform task: #%d", taskInfo.taskId);
+                }
+                repository.updateVisibleFreeformTasks(taskInfo.taskId, false);
+            });
         }
 
         if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
@@ -123,13 +127,15 @@
         mWindowDecorationViewModel.onTaskInfoChanged(state.mTaskInfo);
 
         if (DesktopModeStatus.IS_SUPPORTED) {
-            if (taskInfo.isVisible) {
-                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
-                        "Adding active freeform task: #%d", taskInfo.taskId);
-                mDesktopModeTaskRepository.ifPresent(it -> it.addActiveTask(taskInfo.taskId));
-            }
-            mDesktopModeTaskRepository.ifPresent(
-                    it -> it.updateVisibleFreeformTasks(taskInfo.taskId, taskInfo.isVisible));
+            mDesktopModeTaskRepository.ifPresent(repository -> {
+                if (taskInfo.isVisible) {
+                    if (repository.addActiveTask(taskInfo.taskId)) {
+                        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE,
+                                "Adding active freeform task: #%d", taskInfo.taskId);
+                    }
+                }
+                repository.updateVisibleFreeformTasks(taskInfo.taskId, taskInfo.isVisible);
+            });
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java
index b310ee2..b5ed509 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/BackgroundWindowManager.java
@@ -34,6 +34,7 @@
 import android.os.Binder;
 import android.util.Slog;
 import android.view.ContextThemeWrapper;
+import android.view.Display;
 import android.view.IWindow;
 import android.view.LayoutInflater;
 import android.view.SurfaceControl;
@@ -47,6 +48,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.wm.shell.R;
+import com.android.wm.shell.RootDisplayAreaOrganizer;
 import com.android.wm.shell.common.DisplayLayout;
 
 import java.io.PrintWriter;
@@ -67,11 +69,14 @@
     private SurfaceControl mLeash;
     private View mBackgroundView;
     private @OneHandedState.State int mCurrentState;
+    private RootDisplayAreaOrganizer mRootDisplayAreaOrganizer;
 
-    public BackgroundWindowManager(Context context) {
+    public BackgroundWindowManager(Context context,
+            RootDisplayAreaOrganizer rootDisplayAreaOrganizer) {
         super(context.getResources().getConfiguration(), null /* rootSurface */,
                 null /* hostInputToken */);
         mContext = context;
+        mRootDisplayAreaOrganizer = rootDisplayAreaOrganizer;
         mTransactionFactory = SurfaceControl.Transaction::new;
     }
 
@@ -112,6 +117,7 @@
                 .setOpaque(true)
                 .setName(TAG)
                 .setCallsite("BackgroundWindowManager#attachToParentSurface");
+        mRootDisplayAreaOrganizer.attachToDisplayArea(Display.DEFAULT_DISPLAY, builder);
         mLeash = builder.build();
         b.setParent(mLeash);
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index 679d4ca..ad135d1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -47,6 +47,7 @@
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.logging.UiEventLogger;
 import com.android.wm.shell.R;
+import com.android.wm.shell.RootDisplayAreaOrganizer;
 import com.android.wm.shell.common.DisplayChangeController;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayLayout;
@@ -204,12 +205,14 @@
             DisplayController displayController, DisplayLayout displayLayout,
             TaskStackListenerImpl taskStackListener,
             InteractionJankMonitor jankMonitor, UiEventLogger uiEventLogger,
+            RootDisplayAreaOrganizer rootDisplayAreaOrganizer,
             ShellExecutor mainExecutor, Handler mainHandler) {
         OneHandedSettingsUtil settingsUtil = new OneHandedSettingsUtil();
         OneHandedAccessibilityUtil accessibilityUtil = new OneHandedAccessibilityUtil(context);
         OneHandedTimeoutHandler timeoutHandler = new OneHandedTimeoutHandler(mainExecutor);
         OneHandedState oneHandedState = new OneHandedState();
-        BackgroundWindowManager backgroundWindowManager = new BackgroundWindowManager(context);
+        BackgroundWindowManager backgroundWindowManager =
+                new BackgroundWindowManager(context, rootDisplayAreaOrganizer);
         OneHandedTutorialHandler tutorialHandler = new OneHandedTutorialHandler(context,
                 settingsUtil, windowManager, backgroundWindowManager);
         OneHandedAnimationController animationController =
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java
index 0006244..f707363 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipMenuController.java
@@ -23,6 +23,7 @@
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.RemoteAction;
@@ -73,6 +74,11 @@
     void setAppActions(List<RemoteAction> appActions, RemoteAction closeAction);
 
     /**
+     * Wait until the next frame to run the given Runnable.
+     */
+    void runWithNextFrame(@NonNull Runnable runnable);
+
+    /**
      * Resize the PiP menu with the given bounds. The PiP SurfaceControl is given if there is a
      * need to synchronize the movements on the same frame as PiP.
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index f170e77..2d7c5ce 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -179,8 +179,10 @@
                 // This is necessary in case there was a resize animation ongoing when exit PIP
                 // started, in which case the first resize will be skipped to let the exit
                 // operation handle the final resize out of PIP mode. See b/185306679.
-                finishResize(tx, destinationBounds, direction, animationType);
-                sendOnPipTransitionFinished(direction);
+                finishResizeDelayedIfNeeded(() -> {
+                    finishResize(tx, destinationBounds, direction, animationType);
+                    sendOnPipTransitionFinished(direction);
+                });
             }
         }
 
@@ -196,6 +198,34 @@
         }
     };
 
+    /**
+     * Finishes resizing the PiP, delaying the operation if it has to be synced with the PiP menu.
+     *
+     * This is done to avoid a race condition between the last transaction applied in
+     * onAnimationUpdate and the finishResize in onAnimationEnd. finishResize creates a
+     * WindowContainerTransaction, which is to be applied by WmCore later. It may happen that it
+     * gets applied before the transaction created by the last onAnimationUpdate. As a result of
+     * this, the PiP surface may get scaled after the new bounds are applied by WmCore, which
+     * makes the PiP surface have unexpected bounds. To avoid this, we delay the finishResize
+     * operation until the next frame. This aligns the last onAnimationUpdate transaction with the
+     * WCT application.
+     *
+     * The race only happens when the PiP surface transaction has to be synced with the PiP menu
+     * due to the necessity for a delay when syncing the PiP surface, the PiP menu surface and
+     * the PiP menu contents.
+     */
+    private void finishResizeDelayedIfNeeded(Runnable finishResizeRunnable) {
+        if (!shouldSyncPipTransactionWithMenu()) {
+            finishResizeRunnable.run();
+            return;
+        }
+        mPipMenuController.runWithNextFrame(finishResizeRunnable);
+    }
+
+    private boolean shouldSyncPipTransactionWithMenu() {
+        return mPipMenuController.isMenuVisible();
+    }
+
     @VisibleForTesting
     final PipTransitionController.PipTransitionCallback mPipTransitionCallback =
             new PipTransitionController.PipTransitionCallback() {
@@ -221,7 +251,7 @@
                 @Override
                 public boolean handlePipTransaction(SurfaceControl leash,
                         SurfaceControl.Transaction tx, Rect destinationBounds) {
-                    if (mPipMenuController.isMenuVisible()) {
+                    if (shouldSyncPipTransactionWithMenu()) {
                         mPipMenuController.movePipMenu(leash, tx, destinationBounds);
                         return true;
                     }
@@ -1223,7 +1253,7 @@
         mSurfaceTransactionHelper
                 .crop(tx, mLeash, toBounds)
                 .round(tx, mLeash, mPipTransitionState.isInPip());
-        if (mPipMenuController.isMenuVisible()) {
+        if (shouldSyncPipTransactionWithMenu()) {
             mPipMenuController.resizePipMenu(mLeash, tx, toBounds);
         } else {
             tx.apply();
@@ -1265,7 +1295,7 @@
         mSurfaceTransactionHelper
                 .scale(tx, mLeash, startBounds, toBounds, degrees)
                 .round(tx, mLeash, startBounds, toBounds);
-        if (mPipMenuController.isMenuVisible()) {
+        if (shouldSyncPipTransactionWithMenu()) {
             mPipMenuController.movePipMenu(mLeash, tx, toBounds);
         } else {
             tx.apply();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 2b36b4c..85bad17 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -335,6 +335,7 @@
         final ActivityManager.RunningTaskInfo taskInfo = mPipOrganizer.getTaskInfo();
         if (taskInfo != null) {
             startExpandAnimation(taskInfo, mPipOrganizer.getSurfaceControl(),
+                    mPipBoundsState.getBounds(), mPipBoundsState.getBounds(),
                     new Rect(mExitDestinationBounds), Surface.ROTATION_0);
         }
         mExitDestinationBounds.setEmpty();
@@ -475,6 +476,20 @@
                     taskInfo);
             return;
         }
+
+        // When exiting PiP, the PiP leash may be an Activity of a multi-windowing Task, for which
+        // case it may not be in the screen coordinate.
+        // Reparent the pip leash to the root with max layer so that we can animate it outside of
+        // parent crop, and make sure it is not covered by other windows.
+        final SurfaceControl pipLeash = pipChange.getLeash();
+        startTransaction.reparent(pipLeash, info.getRootLeash());
+        startTransaction.setLayer(pipLeash, Integer.MAX_VALUE);
+        // Note: because of this, the bounds to animate should be translated to the root coordinate.
+        final Point offset = info.getRootOffset();
+        final Rect currentBounds = mPipBoundsState.getBounds();
+        currentBounds.offset(-offset.x, -offset.y);
+        startTransaction.setPosition(pipLeash, currentBounds.left, currentBounds.top);
+
         mFinishCallback = (wct, wctCB) -> {
             mPipOrganizer.onExitPipFinished(taskInfo);
             finishCallback.onTransitionFinished(wct, wctCB);
@@ -496,18 +511,17 @@
             if (displayRotationChange != null) {
                 // Exiting PIP to fullscreen with orientation change.
                 startExpandAndRotationAnimation(info, startTransaction, finishTransaction,
-                        displayRotationChange, taskInfo, pipChange);
+                        displayRotationChange, taskInfo, pipChange, offset);
                 return;
             }
         }
 
         // Set the initial frame as scaling the end to the start.
         final Rect destinationBounds = new Rect(pipChange.getEndAbsBounds());
-        final Point offset = pipChange.getEndRelOffset();
         destinationBounds.offset(-offset.x, -offset.y);
-        startTransaction.setWindowCrop(pipChange.getLeash(), destinationBounds);
-        mSurfaceTransactionHelper.scale(startTransaction, pipChange.getLeash(),
-                destinationBounds, mPipBoundsState.getBounds());
+        startTransaction.setWindowCrop(pipLeash, destinationBounds);
+        mSurfaceTransactionHelper.scale(startTransaction, pipLeash, destinationBounds,
+                currentBounds);
         startTransaction.apply();
 
         // Check if it is fixed rotation.
@@ -532,19 +546,21 @@
                 y = destinationBounds.bottom;
             }
             mSurfaceTransactionHelper.rotateAndScaleWithCrop(finishTransaction,
-                    pipChange.getLeash(), endBounds, endBounds, new Rect(), degree, x, y,
+                    pipLeash, endBounds, endBounds, new Rect(), degree, x, y,
                     true /* isExpanding */, rotationDelta == ROTATION_270 /* clockwise */);
         } else {
             rotationDelta = Surface.ROTATION_0;
         }
-        startExpandAnimation(taskInfo, pipChange.getLeash(), destinationBounds, rotationDelta);
+        startExpandAnimation(taskInfo, pipLeash, currentBounds, currentBounds, destinationBounds,
+                rotationDelta);
     }
 
     private void startExpandAndRotationAnimation(@NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction startTransaction,
             @NonNull SurfaceControl.Transaction finishTransaction,
             @NonNull TransitionInfo.Change displayRotationChange,
-            @NonNull TaskInfo taskInfo, @NonNull TransitionInfo.Change pipChange) {
+            @NonNull TaskInfo taskInfo, @NonNull TransitionInfo.Change pipChange,
+            @NonNull Point offset) {
         final int rotateDelta = deltaRotation(displayRotationChange.getStartRotation(),
                 displayRotationChange.getEndRotation());
 
@@ -556,7 +572,6 @@
         final Rect startBounds = new Rect(pipChange.getStartAbsBounds());
         rotateBounds(startBounds, displayRotationChange.getStartAbsBounds(), rotateDelta);
         final Rect endBounds = new Rect(pipChange.getEndAbsBounds());
-        final Point offset = pipChange.getEndRelOffset();
         startBounds.offset(-offset.x, -offset.y);
         endBounds.offset(-offset.x, -offset.y);
 
@@ -592,11 +607,12 @@
     }
 
     private void startExpandAnimation(final TaskInfo taskInfo, final SurfaceControl leash,
-            final Rect destinationBounds, final int rotationDelta) {
+            final Rect baseBounds, final Rect startBounds, final Rect endBounds,
+            final int rotationDelta) {
         final PipAnimationController.PipTransitionAnimator animator =
-                mPipAnimationController.getAnimator(taskInfo, leash, mPipBoundsState.getBounds(),
-                        mPipBoundsState.getBounds(), destinationBounds, null,
-                        TRANSITION_DIRECTION_LEAVE_PIP, 0 /* startingAngle */, rotationDelta);
+                mPipAnimationController.getAnimator(taskInfo, leash, baseBounds, startBounds,
+                        endBounds, null /* sourceHintRect */, TRANSITION_DIRECTION_LEAVE_PIP,
+                        0 /* startingAngle */, rotationDelta);
         animator.setTransitionDirection(TRANSITION_DIRECTION_LEAVE_PIP)
                 .setPipAnimationCallback(mPipAnimationCallback)
                 .setDuration(mEnterExitAnimationDuration)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
index a0a8f9f..531b9de 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PhonePipMenuController.java
@@ -305,6 +305,18 @@
                 showResizeHandle);
     }
 
+    @Override
+    public void runWithNextFrame(Runnable runnable) {
+        if (mPipMenuView == null || mPipMenuView.getViewRootImpl() == null) {
+            runnable.run();
+        }
+
+        mPipMenuView.getViewRootImpl().registerRtFrameCallback(frame -> {
+            mMainHandler.post(runnable);
+        });
+        mPipMenuView.invalidate();
+    }
+
     /**
      * Move the PiP menu, which does a translation and possibly a scale transformation.
      */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index 616d447..d28a9f3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -612,9 +612,24 @@
                 new DisplayInsetsController.OnInsetsChangedListener() {
                     @Override
                     public void insetsChanged(InsetsState insetsState) {
+                        int oldMaxMovementBound = mPipBoundsState.getMovementBounds().bottom;
                         onDisplayChanged(
                                 mDisplayController.getDisplayLayout(mPipBoundsState.getDisplayId()),
                                 false /* saveRestoreSnapFraction */);
+                        int newMaxMovementBound = mPipBoundsState.getMovementBounds().bottom;
+                        if (!mEnablePipKeepClearAlgorithm) {
+                            int pipTop = mPipBoundsState.getBounds().top;
+                            int diff = newMaxMovementBound - oldMaxMovementBound;
+                            if (diff < 0 && pipTop > newMaxMovementBound) {
+                                // bottom inset has increased, move PiP up if it is too low
+                                mPipMotionHelper.animateToOffset(mPipBoundsState.getBounds(),
+                                        newMaxMovementBound - pipTop);
+                            }
+                            if (diff > 0 && oldMaxMovementBound == pipTop) {
+                                // bottom inset has decreased, move PiP down if it was by the edge
+                                mPipMotionHelper.animateToOffset(mPipBoundsState.getBounds(), diff);
+                            }
+                        }
                     }
                 });
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
index 975d4bb..a9a97be 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipTouchHandler.java
@@ -427,7 +427,7 @@
         // If this is from an IME or shelf adjustment, then we should move the PiP so that it is not
         // occluded by the IME or shelf.
         if (fromImeAdjustment || fromShelfAdjustment) {
-            if (mTouchState.isUserInteracting()) {
+            if (mTouchState.isUserInteracting() && mTouchState.isDragging()) {
                 // Defer the update of the current movement bounds until after the user finishes
                 // touching the screen
             } else if (ENABLE_PIP_KEEP_CLEAR_ALGORITHM) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
index ab7edbf..c39400a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/tv/TvPipMenuController.java
@@ -421,6 +421,18 @@
     }
 
     @Override
+    public void runWithNextFrame(Runnable runnable) {
+        if (mPipMenuView == null || mPipMenuView.getViewRootImpl() == null) {
+            runnable.run();
+        }
+
+        mPipMenuView.getViewRootImpl().registerRtFrameCallback(frame -> {
+            mMainHandler.post(runnable);
+        });
+        mPipMenuView.invalidate();
+    }
+
+    @Override
     public void movePipMenu(SurfaceControl pipLeash, SurfaceControl.Transaction transaction,
             Rect pipDestBounds) {
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 943419b..15a1133 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -115,6 +115,7 @@
 import com.android.wm.shell.common.DisplayImeController;
 import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.ScreenshotUtils;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.TransactionPool;
@@ -846,15 +847,44 @@
     void setSideStagePositionAnimated(@SplitPosition int sideStagePosition) {
         if (mSideStagePosition == sideStagePosition) return;
         SurfaceControl.Transaction t = mTransactionPool.acquire();
+        mTempRect1.setEmpty();
         final StageTaskListener topLeftStage =
                 mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mSideStage : mMainStage;
+        final SurfaceControl topLeftScreenshot = ScreenshotUtils.takeScreenshot(t,
+                topLeftStage.mRootLeash, mTempRect1, Integer.MAX_VALUE - 1);
         final StageTaskListener bottomRightStage =
                 mSideStagePosition == SPLIT_POSITION_TOP_OR_LEFT ? mMainStage : mSideStage;
+        final SurfaceControl bottomRightScreenshot = ScreenshotUtils.takeScreenshot(t,
+                bottomRightStage.mRootLeash, mTempRect1, Integer.MAX_VALUE - 1);
         mSplitLayout.splitSwitching(t, topLeftStage.mRootLeash, bottomRightStage.mRootLeash,
-                () -> {
-                    setSideStagePosition(SplitLayout.reversePosition(mSideStagePosition),
-                            null /* wct */);
-                    mTransactionPool.release(t);
+                insets -> {
+                    WindowContainerTransaction wct = new WindowContainerTransaction();
+                    setSideStagePosition(SplitLayout.reversePosition(mSideStagePosition), wct);
+                    mSyncQueue.queue(wct);
+                    mSyncQueue.runInSync(st -> {
+                        updateSurfaceBounds(mSplitLayout, st, false /* applyResizingOffset */);
+                        st.setPosition(topLeftScreenshot, -insets.left, -insets.top);
+                        st.setPosition(bottomRightScreenshot, insets.left, insets.top);
+
+                        final ValueAnimator va = ValueAnimator.ofFloat(1, 0);
+                        va.addUpdateListener(valueAnimator-> {
+                            final float progress = (float) valueAnimator.getAnimatedValue();
+                            t.setAlpha(topLeftScreenshot, progress);
+                            t.setAlpha(bottomRightScreenshot, progress);
+                            t.apply();
+                        });
+                        va.addListener(new AnimatorListenerAdapter() {
+                            @Override
+                            public void onAnimationEnd(
+                                    @androidx.annotation.NonNull Animator animation) {
+                                t.remove(topLeftScreenshot);
+                                t.remove(bottomRightScreenshot);
+                                t.apply();
+                                mTransactionPool.release(t);
+                            }
+                        });
+                        va.start();
+                    });
                 });
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
index af79386..928e71f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultTransitionHandler.java
@@ -46,6 +46,7 @@
 import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS;
 import static android.window.TransitionInfo.FLAG_FILLS_TASK;
 import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
+import static android.window.TransitionInfo.FLAG_IS_BEHIND_STARTING_WINDOW;
 import static android.window.TransitionInfo.FLAG_IS_DISPLAY;
 import static android.window.TransitionInfo.FLAG_IS_VOICE_INTERACTION;
 import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
@@ -319,6 +320,17 @@
         final int wallpaperTransit = getWallpaperTransitType(info);
         for (int i = info.getChanges().size() - 1; i >= 0; --i) {
             final TransitionInfo.Change change = info.getChanges().get(i);
+            if (change.hasAllFlags(FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY
+                    | FLAG_IS_BEHIND_STARTING_WINDOW)) {
+                // Don't animate embedded activity if it is covered by the starting window.
+                // Non-embedded case still needs animation because the container can still animate
+                // the starting window together, e.g. CLOSE or CHANGE type.
+                continue;
+            }
+            if (change.hasFlags(TransitionInfo.FLAGS_IS_NON_APP_WINDOW)) {
+                // Wallpaper, IME, and system windows don't need any default animations.
+                continue;
+            }
             final boolean isTask = change.getTaskInfo() != null;
             boolean isSeamlessDisplayChange = false;
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index 63d31cd..519ec14 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -175,6 +175,9 @@
     }
 
     private void onInit() {
+        if (Transitions.ENABLE_SHELL_TRANSITIONS) {
+            mOrganizer.shareTransactionQueue();
+        }
         mShellController.addExternalInterface(KEY_EXTRA_SHELL_SHELL_TRANSITIONS,
                 this::createExternalInterface, this);
 
@@ -341,6 +344,15 @@
             final SurfaceControl leash = change.getLeash();
             final int mode = info.getChanges().get(i).getMode();
 
+            if (mode == TRANSIT_TO_FRONT
+                    && ((change.getStartAbsBounds().height() != change.getEndAbsBounds().height()
+                    || change.getStartAbsBounds().width() != change.getEndAbsBounds().width()))) {
+                // When the window is moved to front with a different size, make sure the crop is
+                // updated to prevent it from using the old crop.
+                t.setWindowCrop(leash, change.getEndAbsBounds().width(),
+                        change.getEndAbsBounds().height());
+            }
+
             // Don't move anything that isn't independent within its parents
             if (!TransitionInfo.isIndependent(change, info)) {
                 if (mode == TRANSIT_OPEN || mode == TRANSIT_TO_FRONT || mode == TRANSIT_CHANGE) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index dca516a..36dd8ed 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -26,11 +26,16 @@
 import android.content.Context;
 import android.hardware.input.InputManager;
 import android.os.Handler;
+import android.os.Looper;
 import android.os.SystemClock;
 import android.util.Log;
 import android.util.SparseArray;
 import android.view.Choreographer;
+import android.view.InputChannel;
 import android.view.InputDevice;
+import android.view.InputEvent;
+import android.view.InputEventReceiver;
+import android.view.InputMonitor;
 import android.view.KeyCharacterMap;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
@@ -64,8 +69,11 @@
     private final SyncTransactionQueue mSyncQueue;
     private FreeformTaskTransitionStarter mTransitionStarter;
     private DesktopModeController mDesktopModeController;
+    private EventReceiver mEventReceiver;
+    private InputMonitor mInputMonitor;
 
     private final SparseArray<CaptionWindowDecoration> mWindowDecorByTaskId = new SparseArray<>();
+    private final DragStartListenerImpl mDragStartListener = new DragStartListenerImpl();
 
     public CaptionWindowDecorViewModel(
             Context context,
@@ -108,12 +116,19 @@
                 mSyncQueue);
         mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
 
-        TaskPositioner taskPositioner = new TaskPositioner(mTaskOrganizer, windowDecoration);
+        TaskPositioner taskPositioner = new TaskPositioner(mTaskOrganizer, windowDecoration,
+                mDragStartListener);
         CaptionTouchEventListener touchEventListener =
                 new CaptionTouchEventListener(taskInfo, taskPositioner);
         windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
         windowDecoration.setDragResizeCallback(taskPositioner);
         setupWindowDecorationForTransition(taskInfo, startT, finishT);
+        if (mInputMonitor == null) {
+            mInputMonitor = InputManager.getInstance().monitorGestureInput(
+                    "caption-touch", mContext.getDisplayId());
+            mEventReceiver = new EventReceiver(
+                    mInputMonitor.getInputChannel(), Looper.myLooper());
+        }
         return true;
     }
 
@@ -165,6 +180,7 @@
 
         @Override
         public void onClick(View v) {
+            CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(mTaskId);
             final int id = v.getId();
             if (id == R.id.close_window) {
                 WindowContainerTransaction wct = new WindowContainerTransaction();
@@ -176,6 +192,15 @@
                 }
             } else if (id == R.id.back_button) {
                 injectBackKey();
+            } else if (id == R.id.caption_handle) {
+                decoration.createHandleMenu();
+            } else if (id == R.id.desktop_button) {
+                mDesktopModeController.setDesktopModeActive(true);
+                decoration.closeHandleMenu();
+            } else if (id == R.id.fullscreen_button) {
+                mDesktopModeController.setDesktopModeActive(false);
+                decoration.closeHandleMenu();
+                decoration.setButtonVisibility();
             }
         }
         private void injectBackKey() {
@@ -257,6 +282,36 @@
         }
     }
 
+    // InputEventReceiver to listen for touch input outside of caption bounds
+    private class EventReceiver extends InputEventReceiver {
+        EventReceiver(InputChannel channel, Looper looper) {
+            super(channel, looper);
+        }
+
+        @Override
+        public void onInputEvent(InputEvent event) {
+            boolean handled = false;
+            if (event instanceof MotionEvent
+                    && ((MotionEvent) event).getActionMasked() == MotionEvent.ACTION_UP) {
+                handled = true;
+                CaptionWindowDecorViewModel.this.handleMotionEvent((MotionEvent) event);
+            }
+            finishInputEvent(event, handled);
+        }
+    }
+
+    // If any input received is outside of caption bounds, turn off handle menu
+    private void handleMotionEvent(MotionEvent ev) {
+        int size = mWindowDecorByTaskId.size();
+        for (int i = 0; i < size; i++) {
+            CaptionWindowDecoration decoration = mWindowDecorByTaskId.valueAt(i);
+            if (decoration != null) {
+                decoration.closeHandleMenuIfNeeded(ev);
+            }
+        }
+    }
+
+
     private boolean shouldShowWindowDecor(RunningTaskInfo taskInfo) {
         if (taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) return true;
         return DesktopModeStatus.IS_SUPPORTED
@@ -264,4 +319,11 @@
                 && mDisplayController.getDisplayContext(taskInfo.displayId)
                 .getResources().getConfiguration().smallestScreenWidthDp >= 600;
     }
+
+    private class DragStartListenerImpl implements TaskPositioner.DragStartListener{
+        @Override
+        public void onDragStart(int taskId) {
+            mWindowDecorByTaskId.get(taskId).closeHandleMenu();
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 9d61c14..03cad04 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -20,10 +20,14 @@
 import android.app.WindowConfiguration;
 import android.content.Context;
 import android.content.res.ColorStateList;
+import android.content.res.Resources;
 import android.graphics.Color;
+import android.graphics.Point;
+import android.graphics.Rect;
 import android.graphics.drawable.VectorDrawable;
 import android.os.Handler;
 import android.view.Choreographer;
+import android.view.MotionEvent;
 import android.view.SurfaceControl;
 import android.view.View;
 import android.view.ViewConfiguration;
@@ -58,6 +62,8 @@
 
     private boolean mDesktopActive;
 
+    private AdditionalWindow mHandleMenu;
+
     CaptionWindowDecoration(
             Context context,
             DisplayController displayController,
@@ -123,7 +129,20 @@
         if (isDragResizeable) {
             mRelayoutParams.setOutsets(outsetLeftId, outsetTopId, outsetRightId, outsetBottomId);
         }
+        final Resources resources = mDecorWindowContext.getResources();
+        final Rect taskBounds = taskInfo.configuration.windowConfiguration.getBounds();
+        final int captionHeight = loadDimensionPixelSize(resources,
+                mRelayoutParams.mCaptionHeightId);
+        final int captionWidth = loadDimensionPixelSize(resources,
+                mRelayoutParams.mCaptionWidthId);
+        final int captionLeft = taskBounds.width() / 2
+                - captionWidth / 2;
+        final int captionTop = taskBounds.top
+                <= captionHeight / 2 ? 0 : -captionHeight / 2;
+        mRelayoutParams.setCaptionPosition(captionLeft, captionTop);
+
         relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
+        taskInfo = null; // Clear it just in case we use it accidentally
 
         mTaskOrganizer.applyTransaction(wct);
 
@@ -137,15 +156,14 @@
         }
 
         // If this task is not focused, do not show caption.
-        setCaptionVisibility(taskInfo.isFocused);
+        setCaptionVisibility(mTaskInfo.isFocused);
 
         // Only handle should show if Desktop Mode is inactive.
         boolean desktopCurrentStatus = DesktopModeStatus.isActive(mContext);
-        if (mDesktopActive != desktopCurrentStatus && taskInfo.isFocused) {
+        if (mDesktopActive != desktopCurrentStatus && mTaskInfo.isFocused) {
             mDesktopActive = desktopCurrentStatus;
             setButtonVisibility();
         }
-        taskInfo = null; // Clear it just in case we use it accidentally
 
         if (!isDragResizeable) {
             closeDragResizeListener();
@@ -184,9 +202,22 @@
         back.setOnClickListener(mOnCaptionButtonClickListener);
         View handle = caption.findViewById(R.id.caption_handle);
         handle.setOnTouchListener(mOnCaptionTouchListener);
+        handle.setOnClickListener(mOnCaptionButtonClickListener);
         setButtonVisibility();
     }
 
+    private void setupHandleMenu() {
+        View menu = mHandleMenu.mWindowViewHost.getView();
+        View fullscreen = menu.findViewById(R.id.fullscreen_button);
+        fullscreen.setOnClickListener(mOnCaptionButtonClickListener);
+        View desktop = menu.findViewById(R.id.desktop_button);
+        desktop.setOnClickListener(mOnCaptionButtonClickListener);
+        View split = menu.findViewById(R.id.split_screen_button);
+        split.setOnClickListener(mOnCaptionButtonClickListener);
+        View more = menu.findViewById(R.id.more_button);
+        more.setOnClickListener(mOnCaptionButtonClickListener);
+    }
+
     /**
      * Sets caption visibility based on task focus.
      *
@@ -194,8 +225,9 @@
      */
     private void setCaptionVisibility(boolean visible) {
         int v = visible ? View.VISIBLE : View.GONE;
-        View caption = mResult.mRootView.findViewById(R.id.caption);
-        caption.setVisibility(v);
+        View captionView = mResult.mRootView.findViewById(R.id.caption);
+        captionView.setVisibility(v);
+        if (!visible) closeHandleMenu();
     }
 
     /**
@@ -203,6 +235,7 @@
      *
      */
     public void setButtonVisibility() {
+        mDesktopActive = DesktopModeStatus.isActive(mContext);
         int v = mDesktopActive ? View.VISIBLE : View.GONE;
         View caption = mResult.mRootView.findViewById(R.id.caption);
         View back = caption.findViewById(R.id.back_button);
@@ -220,6 +253,10 @@
         caption.getBackground().setTint(v == View.VISIBLE ? Color.WHITE : Color.TRANSPARENT);
     }
 
+    public boolean isHandleMenuActive() {
+        return mHandleMenu != null;
+    }
+
     private void closeDragResizeListener() {
         if (mDragResizeListener == null) {
             return;
@@ -228,9 +265,67 @@
         mDragResizeListener = null;
     }
 
+    /**
+     * Create and display handle menu window
+     */
+    public void createHandleMenu() {
+        SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+        final Resources resources = mDecorWindowContext.getResources();
+        int x = mRelayoutParams.mCaptionX;
+        int y = mRelayoutParams.mCaptionY;
+        int width = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionWidthId);
+        int height = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionHeightId);
+        String namePrefix = "Caption Menu";
+        mHandleMenu = addWindow(R.layout.caption_handle_menu, namePrefix, t,
+                x - mResult.mDecorContainerOffsetX, y - mResult.mDecorContainerOffsetY,
+                width, height);
+        mSyncQueue.runInSync(transaction -> {
+            transaction.merge(t);
+            t.close();
+        });
+        setupHandleMenu();
+    }
+
+    /**
+     * Close the handle menu window
+     */
+    public void closeHandleMenu() {
+        if (!isHandleMenuActive()) return;
+        mHandleMenu.releaseView();
+        mHandleMenu = null;
+    }
+
+    @Override
+    void releaseViews() {
+        closeHandleMenu();
+        super.releaseViews();
+    }
+
+    /**
+     * Close an open handle menu if input is outside of menu coordinates
+     * @param ev the tapped point to compare against
+     * @return
+     */
+    public void closeHandleMenuIfNeeded(MotionEvent ev) {
+        if (mHandleMenu != null) {
+            Point positionInParent = mTaskOrganizer.getRunningTaskInfo(mTaskInfo.taskId)
+                    .positionInParent;
+            final Resources resources = mDecorWindowContext.getResources();
+            ev.offsetLocation(-mRelayoutParams.mCaptionX, -mRelayoutParams.mCaptionY);
+            ev.offsetLocation(-positionInParent.x, -positionInParent.y);
+            int width = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionWidthId);
+            int height = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionHeightId);
+            if (!(ev.getX() >= 0 && ev.getY()  >= 0
+                    && ev.getX()  <= width && ev.getY()  <= height)) {
+                closeHandleMenu();
+            }
+        }
+    }
+
     @Override
     public void close() {
         closeDragResizeListener();
+        closeHandleMenu();
         super.close();
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
index 27c1011..f0f2db7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskPositioner.java
@@ -42,14 +42,18 @@
     private final Rect mResizeTaskBounds = new Rect();
 
     private int mCtrlType;
+    private DragStartListener mDragStartListener;
 
-    TaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration) {
+    TaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration,
+            DragStartListener dragStartListener) {
         mTaskOrganizer = taskOrganizer;
         mWindowDecoration = windowDecoration;
+        mDragStartListener = dragStartListener;
     }
 
     @Override
     public void onDragResizeStart(int ctrlType, float x, float y) {
+        mDragStartListener.onDragStart(mWindowDecoration.mTaskInfo.taskId);
         mCtrlType = ctrlType;
 
         mTaskBoundsAtDragStart.set(
@@ -97,4 +101,12 @@
             mTaskOrganizer.applyTransaction(wct);
         }
     }
+
+    interface DragStartListener {
+        /**
+         * Inform the implementing class that a drag resize has started
+         * @param taskId id of this positioner's {@link WindowDecoration}
+         */
+        void onDragStart(int taskId);
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index b314163..7ecb3f3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -200,16 +200,17 @@
 
         final Rect taskBounds = taskConfig.windowConfiguration.getBounds();
         final Resources resources = mDecorWindowContext.getResources();
-        final int decorContainerOffsetX = -loadDimensionPixelSize(resources, params.mOutsetLeftId);
-        final int decorContainerOffsetY = -loadDimensionPixelSize(resources, params.mOutsetTopId);
+        outResult.mDecorContainerOffsetX = -loadDimensionPixelSize(resources, params.mOutsetLeftId);
+        outResult.mDecorContainerOffsetY = -loadDimensionPixelSize(resources, params.mOutsetTopId);
         outResult.mWidth = taskBounds.width()
                 + loadDimensionPixelSize(resources, params.mOutsetRightId)
-                - decorContainerOffsetX;
+                - outResult.mDecorContainerOffsetX;
         outResult.mHeight = taskBounds.height()
                 + loadDimensionPixelSize(resources, params.mOutsetBottomId)
-                - decorContainerOffsetY;
+                - outResult.mDecorContainerOffsetY;
         startT.setPosition(
-                        mDecorationContainerSurface, decorContainerOffsetX, decorContainerOffsetY)
+                        mDecorationContainerSurface,
+                        outResult.mDecorContainerOffsetX, outResult.mDecorContainerOffsetY)
                 .setWindowCrop(mDecorationContainerSurface,
                         outResult.mWidth, outResult.mHeight)
                 // TODO(b/244455401): Change the z-order when it's better organized
@@ -252,14 +253,11 @@
         final int captionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId);
         final int captionWidth = loadDimensionPixelSize(resources, params.mCaptionWidthId);
 
-        //Prevent caption from going offscreen if task is too high up
-        final int captionYPos = taskBounds.top <= captionHeight / 2 ? 0 : captionHeight / 2;
-
         startT.setPosition(
-                        mCaptionContainerSurface, -decorContainerOffsetX
-                                + taskBounds.width() / 2 - captionWidth / 2,
-                        -decorContainerOffsetY - captionYPos)
-                .setWindowCrop(mCaptionContainerSurface, taskBounds.width(), captionHeight)
+                        mCaptionContainerSurface,
+                        -outResult.mDecorContainerOffsetX + params.mCaptionX,
+                        -outResult.mDecorContainerOffsetY + params.mCaptionY)
+                .setWindowCrop(mCaptionContainerSurface, captionWidth, captionHeight)
                 .show(mCaptionContainerSurface);
 
         if (mCaptionWindowManager == null) {
@@ -292,7 +290,7 @@
             // Caption insets
             mCaptionInsetsRect.set(taskBounds);
             mCaptionInsetsRect.bottom =
-                    mCaptionInsetsRect.top + captionHeight - captionYPos;
+                    mCaptionInsetsRect.top + captionHeight + params.mCaptionY;
             wct.addRectInsetsProvider(mTaskInfo.token, mCaptionInsetsRect,
                     CAPTION_INSETS_TYPES);
         } else {
@@ -302,10 +300,10 @@
         // Task surface itself
         Point taskPosition = mTaskInfo.positionInParent;
         mTaskSurfaceCrop.set(
-                decorContainerOffsetX,
-                decorContainerOffsetY,
-                outResult.mWidth + decorContainerOffsetX,
-                outResult.mHeight + decorContainerOffsetY);
+                outResult.mDecorContainerOffsetX,
+                outResult.mDecorContainerOffsetY,
+                outResult.mWidth + outResult.mDecorContainerOffsetX,
+                outResult.mHeight + outResult.mDecorContainerOffsetY);
         startT.show(mTaskSurface);
         finishT.setPosition(mTaskSurface, taskPosition.x, taskPosition.y)
                 .setCrop(mTaskSurface, mTaskSurfaceCrop);
@@ -326,7 +324,7 @@
         return true;
     }
 
-    private void releaseViews() {
+    void releaseViews() {
         if (mViewHost != null) {
             mViewHost.release();
             mViewHost = null;
@@ -369,20 +367,60 @@
         releaseViews();
     }
 
-    private static int loadDimensionPixelSize(Resources resources, int resourceId) {
+    static int loadDimensionPixelSize(Resources resources, int resourceId) {
         if (resourceId == Resources.ID_NULL) {
             return 0;
         }
         return resources.getDimensionPixelSize(resourceId);
     }
 
-    private static float loadDimension(Resources resources, int resourceId) {
+    static float loadDimension(Resources resources, int resourceId) {
         if (resourceId == Resources.ID_NULL) {
             return 0;
         }
         return resources.getDimension(resourceId);
     }
 
+    /**
+     * Create a window associated with this WindowDecoration.
+     * Note that subclass must dispose of this when the task is hidden/closed.
+     * @param layoutId layout to make the window from
+     * @param t the transaction to apply
+     * @param xPos x position of new window
+     * @param yPos y position of new window
+     * @param width width of new window
+     * @param height height of new window
+     * @return
+     */
+    AdditionalWindow addWindow(int layoutId, String namePrefix,
+            SurfaceControl.Transaction t, int xPos, int yPos, int width, int height) {
+        final SurfaceControl.Builder builder = mSurfaceControlBuilderSupplier.get();
+        SurfaceControl windowSurfaceControl = builder
+                .setName(namePrefix + " of Task=" + mTaskInfo.taskId)
+                .setContainerLayer()
+                .setParent(mDecorationContainerSurface)
+                .build();
+        View v = LayoutInflater.from(mDecorWindowContext).inflate(layoutId, null);
+
+        t.setPosition(
+                windowSurfaceControl, xPos, yPos)
+                .setWindowCrop(windowSurfaceControl, width, height)
+                .show(windowSurfaceControl);
+        final WindowManager.LayoutParams lp =
+                new WindowManager.LayoutParams(width, height,
+                        WindowManager.LayoutParams.TYPE_APPLICATION,
+                        WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, PixelFormat.TRANSPARENT);
+        lp.setTitle("Additional window of Task=" + mTaskInfo.taskId);
+        lp.setTrustedOverlay();
+        WindowlessWindowManager windowManager = new WindowlessWindowManager(mTaskInfo.configuration,
+                windowSurfaceControl, null /* hostInputToken */);
+        SurfaceControlViewHost viewHost = mSurfaceControlViewHostFactory
+                .create(mDecorWindowContext, mDisplay, windowManager);
+        viewHost.setView(v, lp);
+        return new AdditionalWindow(windowSurfaceControl, viewHost,
+                mSurfaceControlTransactionSupplier);
+    }
+
     static class RelayoutParams{
         RunningTaskInfo mRunningTaskInfo;
         int mLayoutResId;
@@ -395,6 +433,9 @@
         int mOutsetLeftId;
         int mOutsetRightId;
 
+        int mCaptionX;
+        int mCaptionY;
+
         void setOutsets(int leftId, int topId, int rightId, int bottomId) {
             mOutsetLeftId = leftId;
             mOutsetTopId = topId;
@@ -402,6 +443,11 @@
             mOutsetBottomId = bottomId;
         }
 
+        void setCaptionPosition(int left, int top) {
+            mCaptionX = left;
+            mCaptionY = top;
+        }
+
         void reset() {
             mLayoutResId = Resources.ID_NULL;
             mCaptionHeightId = Resources.ID_NULL;
@@ -412,6 +458,9 @@
             mOutsetBottomId = Resources.ID_NULL;
             mOutsetLeftId = Resources.ID_NULL;
             mOutsetRightId = Resources.ID_NULL;
+
+            mCaptionX = 0;
+            mCaptionY = 0;
         }
     }
 
@@ -419,10 +468,14 @@
         int mWidth;
         int mHeight;
         T mRootView;
+        int mDecorContainerOffsetX;
+        int mDecorContainerOffsetY;
 
         void reset() {
             mWidth = 0;
             mHeight = 0;
+            mDecorContainerOffsetX = 0;
+            mDecorContainerOffsetY = 0;
             mRootView = null;
         }
     }
@@ -432,4 +485,41 @@
             return new SurfaceControlViewHost(c, d, wmm);
         }
     }
+
+    /**
+     * Subclass for additional windows associated with this WindowDecoration
+     */
+    static class AdditionalWindow {
+        SurfaceControl mWindowSurface;
+        SurfaceControlViewHost mWindowViewHost;
+        Supplier<SurfaceControl.Transaction> mTransactionSupplier;
+
+        private AdditionalWindow(SurfaceControl surfaceControl,
+                SurfaceControlViewHost surfaceControlViewHost,
+                Supplier<SurfaceControl.Transaction> transactionSupplier) {
+            mWindowSurface = surfaceControl;
+            mWindowViewHost = surfaceControlViewHost;
+            mTransactionSupplier = transactionSupplier;
+        }
+
+        void releaseView() {
+            WindowlessWindowManager windowManager = mWindowViewHost.getWindowlessWM();
+
+            if (mWindowViewHost != null) {
+                mWindowViewHost.release();
+                mWindowViewHost = null;
+            }
+            windowManager = null;
+            final SurfaceControl.Transaction t = mTransactionSupplier.get();
+            boolean released = false;
+            if (mWindowSurface != null) {
+                t.remove(mWindowSurface);
+                mWindowSurface = null;
+                released = true;
+            }
+            if (released) {
+                t.apply();
+            }
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/tests/OWNERS b/libs/WindowManager/Shell/tests/OWNERS
index f4efc37..1c28c3d 100644
--- a/libs/WindowManager/Shell/tests/OWNERS
+++ b/libs/WindowManager/Shell/tests/OWNERS
@@ -6,3 +6,4 @@
 lbill@google.com
 madym@google.com
 hwwang@google.com
+chenghsiuchang@google.com
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
index 6f1ff99..8765ad1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonAssertions.kt
@@ -45,14 +45,23 @@
 fun FlickerTestParameter.splitScreenEntered(
     component1: IComponentMatcher,
     component2: IComponentMatcher,
-    fromOtherApp: Boolean
+    fromOtherApp: Boolean,
+    appExistAtStart: Boolean = true
 ) {
     if (fromOtherApp) {
-        appWindowIsInvisibleAtStart(component1)
+        if (appExistAtStart) {
+            appWindowIsInvisibleAtStart(component1)
+        } else {
+            appWindowIsNotContainAtStart(component1)
+        }
     } else {
         appWindowIsVisibleAtStart(component1)
     }
-    appWindowIsInvisibleAtStart(component2)
+    if (appExistAtStart) {
+        appWindowIsInvisibleAtStart(component2)
+    } else {
+        appWindowIsNotContainAtStart(component2)
+    }
     splitScreenDividerIsInvisibleAtStart()
 
     appWindowIsVisibleAtEnd(component1)
@@ -315,6 +324,10 @@
     assertWmEnd { this.isAppWindowInvisible(component) }
 }
 
+fun FlickerTestParameter.appWindowIsNotContainAtStart(component: IComponentMatcher) {
+    assertWmStart { this.notContains(component) }
+}
+
 fun FlickerTestParameter.appWindowKeepVisible(component: IComponentMatcher) {
     assertWm { this.isAppWindowVisible(component) }
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt
index 7997892..651d935 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/CommonConstants.kt
@@ -21,6 +21,7 @@
 import com.android.server.wm.traces.common.ComponentNameMatcher
 
 const val SYSTEM_UI_PACKAGE_NAME = "com.android.systemui"
+const val LAUNCHER_UI_PACKAGE_NAME = "com.google.android.apps.nexuslauncher"
 val APP_PAIR_SPLIT_DIVIDER_COMPONENT = ComponentNameMatcher("", "AppPairSplitDivider#")
 val DOCKED_STACK_DIVIDER_COMPONENT = ComponentNameMatcher("", "DockedStackDivider#")
 val SPLIT_SCREEN_DIVIDER_COMPONENT = ComponentNameMatcher("", "StageCoordinatorSplitDivider#")
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
index f802539..7546a55 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AutoEnterPipOnGoToHomeTest.kt
@@ -80,7 +80,7 @@
             transitions { tapl.goHome() }
         }
 
-    @FlakyTest
+    @FlakyTest(bugId = 256863309)
     @Test
     override fun pipLayerReduces() {
         testSpec.assertLayers {
@@ -108,14 +108,6 @@
         }
     }
 
-    @FlakyTest(bugId = 239807171)
-    @Test
-    override fun pipAppLayerAlwaysVisible() = super.pipAppLayerAlwaysVisible()
-
-    @FlakyTest(bugId = 239807171)
-    @Test
-    override fun pipLayerRemainInsideVisibleBounds() = super.pipLayerRemainInsideVisibleBounds()
-
     @Presubmit
     @Test
     override fun focusChanges() {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
index 9e76575..2bce8e45 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/CopyContentInSplit.kt
@@ -53,7 +53,7 @@
     override val transition: FlickerBuilder.() -> Unit
         get() = {
             super.transition(this)
-            setup { SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, textEditApp) }
+            setup { SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, textEditApp) }
             transitions {
                 SplitScreenUtils.copyContentInSplit(
                     instrumentation,
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
index 45eae2e..4757498 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByDivider.kt
@@ -55,7 +55,7 @@
         get() = {
             super.transition(this)
             setup {
-                SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
+                SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
             }
             transitions {
                 if (tapl.isTablet) {
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
index 6cfbb47..1d61955 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DismissSplitScreenByGoHome.kt
@@ -52,7 +52,7 @@
         get() = {
             super.transition(this)
             setup {
-                SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
+                SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
             }
             transitions {
                 tapl.goHome()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
index a80c88a..8d771fe 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/DragDividerToResize.kt
@@ -56,7 +56,7 @@
         get() = {
             super.transition(this)
             setup {
-                SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
+                SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
             }
             transitions {
                 SplitScreenUtils.dragDividerToResizeAndWait(device, wmHelper)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
new file mode 100644
index 0000000..e9c765a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenByDragFromShortcut.kt
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen
+
+import android.view.WindowManagerPolicyConstants
+import android.platform.test.annotations.Postsubmit
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.layerBecomesVisible
+import com.android.wm.shell.flicker.layerIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitAppLayerBoundsBecomesVisibleByDrag
+import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
+import com.android.wm.shell.flicker.splitScreenEntered
+import org.junit.Assume
+import org.junit.Before
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test enter split screen by dragging a shortcut.
+ * This test is only for large screen devices.
+ *
+ * To run this test: `atest WMShellFlickerTests:EnterSplitScreenByDragFromShortcut`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class EnterSplitScreenByDragFromShortcut(
+    testSpec: FlickerTestParameter
+) : SplitScreenBase(testSpec) {
+
+    @Before
+    fun before() {
+        Assume.assumeTrue(testSpec.isTablet)
+    }
+
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            super.transition(this)
+            setup {
+                tapl.goHome()
+                SplitScreenUtils.createShortcutOnHotseatIfNotExist(tapl, secondaryApp.appName)
+                primaryApp.launchViaIntent(wmHelper)
+            }
+            transitions {
+                tapl.launchedAppState.taskbar
+                    .getAppIcon(secondaryApp.appName)
+                    .openDeepShortcutMenu()
+                    .getMenuItem("Split Screen Secondary Activity")
+                    .dragToSplitscreen(secondaryApp.`package`, primaryApp.`package`)
+                SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+            }
+        }
+
+    @Postsubmit
+    @Test
+    fun cujCompleted() = testSpec.splitScreenEntered(primaryApp, secondaryApp,
+        fromOtherApp = false, appExistAtStart = false)
+
+    @Postsubmit
+    @Test
+    fun splitScreenDividerBecomesVisible() = testSpec.splitScreenDividerBecomesVisible()
+
+    @Postsubmit
+    @Test
+    fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp)
+
+    @Postsubmit
+    @Test
+    fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp)
+
+    @Postsubmit
+    @Test
+    fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+        primaryApp, landscapePosLeft = false, portraitPosTop = false)
+
+    @Postsubmit
+    @Test
+    fun secondaryAppBoundsBecomesVisible() = testSpec.splitAppLayerBoundsBecomesVisibleByDrag(
+        secondaryApp)
+
+    @Postsubmit
+    @Test
+    fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp)
+
+    @Postsubmit
+    @Test
+    fun secondaryAppWindowBecomesVisible() {
+        testSpec.assertWm {
+            this.notContains(secondaryApp)
+                .then()
+                .isAppWindowInvisible(secondaryApp, isOptional = true)
+                .then()
+                .isAppWindowVisible(secondaryApp)
+        }
+    }
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun entireScreenCovered() =
+        super.entireScreenCovered()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun navBarLayerIsVisibleAtStartAndEnd() =
+        super.navBarLayerIsVisibleAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun navBarLayerPositionAtStartAndEnd() =
+        super.navBarLayerPositionAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun navBarWindowIsAlwaysVisible() =
+        super.navBarWindowIsAlwaysVisible()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun statusBarLayerIsVisibleAtStartAndEnd() =
+        super.statusBarLayerIsVisibleAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun statusBarLayerPositionAtStartAndEnd() =
+        super.statusBarLayerPositionAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun statusBarWindowIsAlwaysVisible() =
+        super.statusBarWindowIsAlwaysVisible()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun taskBarLayerIsVisibleAtStartAndEnd() =
+        super.taskBarLayerIsVisibleAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun taskBarWindowIsAlwaysVisible() =
+        super.taskBarWindowIsAlwaysVisible()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+        super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+    /** {@inheritDoc} */
+    @Postsubmit
+    @Test
+    override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+        super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): List<FlickerTestParameter> {
+            return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests(
+                // TODO(b/176061063):The 3 buttons of nav bar do not exist in the hierarchy.
+                supportedNavigationModes =
+                    listOf(WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL_OVERLAY))
+        }
+    }
+}
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
index 936afa9..fb7b8b7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/EnterSplitScreenFromOverview.kt
@@ -34,7 +34,6 @@
 import com.android.wm.shell.flicker.splitScreenDividerBecomesVisible
 import com.android.wm.shell.flicker.splitScreenEntered
 import org.junit.Assume
-import org.junit.Before
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -55,7 +54,6 @@
         get() = {
             super.transition(this)
             setup {
-                tapl.workspace.switchToOverview().dismissAllTasks()
                 primaryApp.launchViaIntent(wmHelper)
                 secondaryApp.launchViaIntent(wmHelper)
                 tapl.goHome()
@@ -65,7 +63,7 @@
                     .waitForAndVerify()
             }
             transitions {
-                SplitScreenUtils.splitFromOverview(tapl)
+                SplitScreenUtils.splitFromOverview(tapl, device)
                 SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
             }
         }
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt
index e6d6379..c841333 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenBase.kt
@@ -34,6 +34,7 @@
                 tapl.setEnableRotation(true)
                 setRotation(testSpec.startRotation)
                 tapl.setExpectedRotation(testSpec.startRotation)
+                tapl.workspace.switchToOverview().dismissAllTasks()
             }
             teardown {
                 primaryApp.exit(wmHelper)
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt
index 6453ed8..ead451f 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SplitScreenUtils.kt
@@ -25,6 +25,7 @@
 import androidx.test.uiautomator.By
 import androidx.test.uiautomator.BySelector
 import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.UiObject2
 import androidx.test.uiautomator.Until
 import com.android.launcher3.tapl.LauncherInstrumentation
 import com.android.server.wm.flicker.helpers.ImeAppHelper
@@ -38,13 +39,16 @@
 import com.android.server.wm.traces.common.IComponentNameMatcher
 import com.android.server.wm.traces.parser.toFlickerComponent
 import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
+import com.android.wm.shell.flicker.LAUNCHER_UI_PACKAGE_NAME
 import com.android.wm.shell.flicker.SYSTEM_UI_PACKAGE_NAME
+import java.util.Collections
 
 internal object SplitScreenUtils {
     private const val TIMEOUT_MS = 3_000L
     private const val DRAG_DURATION_MS = 1_000L
     private const val NOTIFICATION_SCROLLER = "notification_stack_scroller"
     private const val DIVIDER_BAR = "docked_divider_handle"
+    private const val OVERVIEW_SNAPSHOT = "snapshot"
     private const val GESTURE_STEP_MS = 16L
     private const val LONG_PRESS_TIME_MS = 100L
     private val SPLIT_DECOR_MANAGER = ComponentNameMatcher("", "SplitDecorManager#")
@@ -55,6 +59,8 @@
         get() = By.text("Flicker Test Notification")
     private val dividerBarSelector: BySelector
         get() = By.res(SYSTEM_UI_PACKAGE_NAME, DIVIDER_BAR)
+    private val overviewSnapshotSelector: BySelector
+        get() = By.res(LAUNCHER_UI_PACKAGE_NAME, OVERVIEW_SNAPSHOT)
 
     fun getPrimary(instrumentation: Instrumentation): StandardAppHelper =
         SimpleAppHelper(
@@ -94,24 +100,39 @@
     fun enterSplit(
         wmHelper: WindowManagerStateHelper,
         tapl: LauncherInstrumentation,
+        device: UiDevice,
         primaryApp: StandardAppHelper,
         secondaryApp: StandardAppHelper
     ) {
-        tapl.workspace.switchToOverview().dismissAllTasks()
         primaryApp.launchViaIntent(wmHelper)
         secondaryApp.launchViaIntent(wmHelper)
         tapl.goHome()
         wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
-        splitFromOverview(tapl)
+        splitFromOverview(tapl, device)
         waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
     }
 
-    fun splitFromOverview(tapl: LauncherInstrumentation) {
+    fun splitFromOverview(tapl: LauncherInstrumentation, device: UiDevice) {
         // Note: The initial split position in landscape is different between tablet and phone.
         // In landscape, tablet will let the first app split to right side, and phone will
         // split to left side.
         if (tapl.isTablet) {
-            tapl.workspace.switchToOverview().overviewActions.clickSplit().currentTask.open()
+            // TAPL's currentTask on tablet is sometimes not what we expected if the overview
+            // contains more than 3 task views. We need to use uiautomator directly to find the
+            // second task to split.
+            tapl.workspace.switchToOverview().overviewActions.clickSplit()
+            val snapshots = device.wait(Until.findObjects(overviewSnapshotSelector), TIMEOUT_MS)
+            if (snapshots == null || snapshots.size < 1) {
+                error("Fail to find a overview snapshot to split.")
+            }
+
+            // Find the second task in the upper right corner in split select mode by sorting
+            // 'left' in descending order and 'top' in ascending order.
+            Collections.sort(snapshots, { t1: UiObject2, t2: UiObject2 ->
+                t2.getVisibleBounds().left - t1.getVisibleBounds().left})
+            Collections.sort(snapshots, { t1: UiObject2, t2: UiObject2 ->
+                t1.getVisibleBounds().top - t2.getVisibleBounds().top})
+            snapshots[0].click()
         } else {
             tapl.workspace
                 .switchToOverview()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
index 73159c9..f7610c4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchAppByDoubleTapDivider.kt
@@ -56,7 +56,7 @@
         get() = {
             super.transition(this)
             setup {
-                SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
+                SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
             }
             transitions {
                 SplitScreenUtils.doubleTapDividerToSwitch(device)
@@ -145,15 +145,19 @@
         // robust enough to get the correct end state.
     }
 
+    @Presubmit
     @Test
     fun splitScreenDividerKeepVisible() = testSpec.layerKeepVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
 
+    @Presubmit
     @Test
     fun primaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(primaryApp)
 
+    @Presubmit
     @Test
     fun secondaryAppLayerIsVisibleAtEnd() = testSpec.layerIsVisibleAtEnd(secondaryApp)
 
+    @Presubmit
     @Test
     fun primaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
         primaryApp,
@@ -161,6 +165,7 @@
         portraitPosTop = true
     )
 
+    @Presubmit
     @Test
     fun secondaryAppBoundsIsVisibleAtEnd() = testSpec.splitAppLayerBoundsIsVisibleAtEnd(
         secondaryApp,
@@ -168,9 +173,11 @@
         portraitPosTop = false
     )
 
+    @Presubmit
     @Test
     fun primaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(primaryApp)
 
+    @Presubmit
     @Test
     fun secondaryAppWindowIsVisibleAtEnd() = testSpec.appWindowIsVisibleAtEnd(secondaryApp)
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
index 553840c..993dba2 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromAnotherApp.kt
@@ -52,7 +52,7 @@
         get() = {
             super.transition(this)
             setup {
-                SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
+                SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
 
                 thirdApp.launchViaIntent(wmHelper)
                 wmHelper.StateSyncBuilder().withWindowSurfaceAppeared(thirdApp).waitForAndVerify()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
index 1f117d0..2a552cd 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromHome.kt
@@ -51,7 +51,7 @@
         get() = {
             super.transition(this)
             setup {
-                SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
+                SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
 
                 tapl.goHome()
                 wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
index d7b3ec2..7f81bae 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBackToSplitFromRecent.kt
@@ -51,7 +51,7 @@
         get() = {
             super.transition(this)
             setup {
-                SplitScreenUtils.enterSplit(wmHelper, tapl, primaryApp, secondaryApp)
+                SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
 
                 tapl.goHome()
                 wmHelper.StateSyncBuilder().withHomeActivityVisible().waitForAndVerify()
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
new file mode 100644
index 0000000..d84954d
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/splitscreen/SwitchBetweenSplitPairs.kt
@@ -0,0 +1,248 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.flicker.splitscreen
+
+import android.platform.test.annotations.FlakyTest
+import android.platform.test.annotations.Postsubmit
+import android.platform.test.annotations.Presubmit
+import androidx.test.filters.RequiresDevice
+import com.android.server.wm.flicker.FlickerParametersRunnerFactory
+import com.android.server.wm.flicker.FlickerTestParameter
+import com.android.server.wm.flicker.FlickerTestParameterFactory
+import com.android.server.wm.flicker.dsl.FlickerBuilder
+import com.android.wm.shell.flicker.SPLIT_SCREEN_DIVIDER_COMPONENT
+import com.android.wm.shell.flicker.appWindowBecomesInvisible
+import com.android.wm.shell.flicker.appWindowBecomesVisible
+import com.android.wm.shell.flicker.appWindowIsInvisibleAtEnd
+import com.android.wm.shell.flicker.appWindowIsVisibleAtStart
+import com.android.wm.shell.flicker.appWindowIsVisibleAtEnd
+import com.android.wm.shell.flicker.layerBecomesInvisible
+import com.android.wm.shell.flicker.layerBecomesVisible
+import com.android.wm.shell.flicker.splitAppLayerBoundsIsVisibleAtEnd
+import com.android.wm.shell.flicker.splitAppLayerBoundsSnapToDivider
+import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtStart
+import com.android.wm.shell.flicker.splitScreenDividerIsVisibleAtEnd
+import org.junit.FixMethodOrder
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.MethodSorters
+import org.junit.runners.Parameterized
+
+/**
+ * Test quick switch between two split pairs.
+ *
+ * To run this test: `atest WMShellFlickerTests:SwitchBetweenSplitPairs`
+ */
+@RequiresDevice
+@RunWith(Parameterized::class)
+@Parameterized.UseParametersRunnerFactory(FlickerParametersRunnerFactory::class)
+@FixMethodOrder(MethodSorters.NAME_ASCENDING)
+class SwitchBetweenSplitPairs(testSpec: FlickerTestParameter) : SplitScreenBase(testSpec) {
+    private val thirdApp = SplitScreenUtils.getIme(instrumentation)
+    private val fourthApp = SplitScreenUtils.getSendNotification(instrumentation)
+
+    override val transition: FlickerBuilder.() -> Unit
+        get() = {
+            super.transition(this)
+            setup {
+                SplitScreenUtils.enterSplit(wmHelper, tapl, device, primaryApp, secondaryApp)
+                SplitScreenUtils.enterSplit(wmHelper, tapl, device, thirdApp, fourthApp)
+                SplitScreenUtils.waitForSplitComplete(wmHelper, thirdApp, fourthApp)
+            }
+            transitions {
+                tapl.launchedAppState.quickSwitchToPreviousApp()
+                SplitScreenUtils.waitForSplitComplete(wmHelper, primaryApp, secondaryApp)
+            }
+            teardown {
+                thirdApp.exit(wmHelper)
+                fourthApp.exit(wmHelper)
+            }
+        }
+
+    @Postsubmit
+    @Test
+    fun cujCompleted() {
+        testSpec.appWindowIsVisibleAtStart(thirdApp)
+        testSpec.appWindowIsVisibleAtStart(fourthApp)
+        testSpec.splitScreenDividerIsVisibleAtStart()
+
+        testSpec.appWindowIsVisibleAtEnd(primaryApp)
+        testSpec.appWindowIsVisibleAtEnd(secondaryApp)
+        testSpec.appWindowIsInvisibleAtEnd(thirdApp)
+        testSpec.appWindowIsInvisibleAtEnd(fourthApp)
+        testSpec.splitScreenDividerIsVisibleAtEnd()
+    }
+
+    @Postsubmit
+    @Test
+    fun splitScreenDividerInvisibleAtMiddle() =
+        testSpec.assertLayers {
+            this.isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+                .then()
+                .isInvisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+                .then()
+                .isVisible(SPLIT_SCREEN_DIVIDER_COMPONENT)
+        }
+
+    @FlakyTest(bugId = 247095572)
+    @Test
+    fun primaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(primaryApp)
+
+    @FlakyTest(bugId = 247095572)
+    @Test
+    fun secondaryAppLayerBecomesVisible() = testSpec.layerBecomesVisible(secondaryApp)
+
+    @FlakyTest(bugId = 247095572)
+    @Test
+    fun thirdAppLayerBecomesInvisible() = testSpec.layerBecomesInvisible(thirdApp)
+
+    @FlakyTest(bugId = 247095572)
+    @Test
+    fun fourthAppLayerBecomesInvisible() = testSpec.layerBecomesInvisible(fourthApp)
+
+    @Postsubmit
+    @Test
+    fun primaryAppBoundsIsVisibleAtEnd() =
+        testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+            primaryApp,
+            landscapePosLeft = tapl.isTablet,
+            portraitPosTop = false
+        )
+
+    @Postsubmit
+    @Test
+    fun secondaryAppBoundsIsVisibleAtEnd() =
+        testSpec.splitAppLayerBoundsIsVisibleAtEnd(
+            secondaryApp,
+            landscapePosLeft = !tapl.isTablet,
+            portraitPosTop = true
+        )
+
+    @Postsubmit
+    @Test
+    fun thirdAppBoundsIsVisibleAtBegin() =
+        testSpec.assertLayersStart {
+            this.splitAppLayerBoundsSnapToDivider(
+                thirdApp,
+                landscapePosLeft = tapl.isTablet,
+                portraitPosTop = false,
+                testSpec.startRotation
+            )
+        }
+
+    @Postsubmit
+    @Test
+    fun fourthAppBoundsIsVisibleAtBegin() =
+        testSpec.assertLayersStart {
+            this.splitAppLayerBoundsSnapToDivider(
+                fourthApp,
+                landscapePosLeft = !tapl.isTablet,
+                portraitPosTop = true,
+                testSpec.startRotation
+            )
+        }
+
+    @Postsubmit
+    @Test
+    fun primaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(primaryApp)
+
+    @Postsubmit
+    @Test
+    fun secondaryAppWindowBecomesVisible() = testSpec.appWindowBecomesVisible(secondaryApp)
+
+    @Postsubmit
+    @Test
+    fun thirdAppWindowBecomesVisible() = testSpec.appWindowBecomesInvisible(thirdApp)
+
+    @Postsubmit
+    @Test
+    fun fourthAppWindowBecomesVisible() = testSpec.appWindowBecomesInvisible(fourthApp)
+
+    /** {@inheritDoc} */
+    @FlakyTest(bugId = 251268711)
+    @Test
+    override fun entireScreenCovered() =
+        super.entireScreenCovered()
+
+    /** {@inheritDoc} */
+    @Presubmit
+    @Test
+    override fun navBarLayerIsVisibleAtStartAndEnd() =
+        super.navBarLayerIsVisibleAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @FlakyTest(bugId = 206753786)
+    @Test
+    override fun navBarLayerPositionAtStartAndEnd() =
+        super.navBarLayerPositionAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Presubmit
+    @Test
+    override fun navBarWindowIsAlwaysVisible() =
+        super.navBarWindowIsAlwaysVisible()
+
+    /** {@inheritDoc} */
+    @Presubmit
+    @Test
+    override fun statusBarLayerIsVisibleAtStartAndEnd() =
+        super.statusBarLayerIsVisibleAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Presubmit
+    @Test
+    override fun statusBarLayerPositionAtStartAndEnd() =
+        super.statusBarLayerPositionAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Presubmit
+    @Test
+    override fun statusBarWindowIsAlwaysVisible() =
+        super.statusBarWindowIsAlwaysVisible()
+
+    /** {@inheritDoc} */
+    @Presubmit
+    @Test
+    override fun taskBarLayerIsVisibleAtStartAndEnd() =
+        super.taskBarLayerIsVisibleAtStartAndEnd()
+
+    /** {@inheritDoc} */
+    @Presubmit
+    @Test
+    override fun taskBarWindowIsAlwaysVisible() =
+        super.taskBarWindowIsAlwaysVisible()
+
+    /** {@inheritDoc} */
+    @FlakyTest
+    @Test
+    override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
+        super.visibleLayersShownMoreThanOneConsecutiveEntry()
+
+    /** {@inheritDoc} */
+    @Presubmit
+    @Test
+    override fun visibleWindowsShownMoreThanOneConsecutiveEntry() =
+        super.visibleWindowsShownMoreThanOneConsecutiveEntry()
+
+    companion object {
+        @Parameterized.Parameters(name = "{0}")
+        @JvmStatic
+        fun getParams(): List<FlickerTestParameter> {
+            return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/BackgroundWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/BackgroundWindowManagerTest.java
index f3f7067..11948dbf 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/BackgroundWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/BackgroundWindowManagerTest.java
@@ -24,6 +24,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.wm.shell.RootDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.common.DisplayLayout;
 
@@ -41,11 +42,13 @@
     private BackgroundWindowManager mBackgroundWindowManager;
     @Mock
     private DisplayLayout  mMockDisplayLayout;
+    @Mock
+    private RootDisplayAreaOrganizer mRootDisplayAreaOrganizer;
 
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
-        mBackgroundWindowManager = new BackgroundWindowManager(mContext);
+        mBackgroundWindowManager = new BackgroundWindowManager(mContext, mRootDisplayAreaOrganizer);
         mBackgroundWindowManager.onDisplayChanged(mMockDisplayLayout);
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index 55883ab..d01f3d3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -39,6 +39,7 @@
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
 
+import androidx.test.annotation.UiThreadTest;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
@@ -110,6 +111,7 @@
     }
 
     @Test
+    @UiThreadTest
     public void instantiateController_registerDumpCallback() {
         doReturn(mMainExecutor).when(mTaskOrganizer).getExecutor();
         when(mDisplayController.getDisplayLayout(anyInt())).thenReturn(new DisplayLayout());
@@ -118,6 +120,7 @@
     }
 
     @Test
+    @UiThreadTest
     public void instantiateController_registerCommandCallback() {
         doReturn(mMainExecutor).when(mTaskOrganizer).getExecutor();
         when(mDisplayController.getDisplayLayout(anyInt())).thenReturn(new DisplayLayout());
@@ -126,6 +129,7 @@
     }
 
     @Test
+    @UiThreadTest
     public void testControllerRegistersKeyguardChangeListener() {
         doReturn(mMainExecutor).when(mTaskOrganizer).getExecutor();
         when(mDisplayController.getDisplayLayout(anyInt())).thenReturn(new DisplayLayout());
@@ -134,6 +138,7 @@
     }
 
     @Test
+    @UiThreadTest
     public void instantiateController_addExternalInterface() {
         doReturn(mMainExecutor).when(mTaskOrganizer).getExecutor();
         when(mDisplayController.getDisplayLayout(anyInt())).thenReturn(new DisplayLayout());
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index 8350870..3569860 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -50,6 +50,7 @@
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
+import androidx.test.annotation.UiThreadTest;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
@@ -113,6 +114,7 @@
     private StageCoordinator mStageCoordinator;
 
     @Before
+    @UiThreadTest
     public void setup() {
         MockitoAnnotations.initMocks(this);
         mStageCoordinator = spy(new StageCoordinator(mContext, DEFAULT_DISPLAY, mSyncQueue,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index 4d37e5d..15181b1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -35,6 +35,7 @@
 
 import android.app.ActivityManager;
 import android.content.Context;
+import android.content.res.Resources;
 import android.graphics.Color;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -64,6 +65,7 @@
 import org.mockito.ArgumentCaptor;
 import org.mockito.InOrder;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -102,12 +104,14 @@
     private final List<SurfaceControl.Builder> mMockSurfaceControlBuilders = new ArrayList<>();
     private SurfaceControl.Transaction mMockSurfaceControlStartT;
     private SurfaceControl.Transaction mMockSurfaceControlFinishT;
+    private SurfaceControl.Transaction mMockSurfaceControlAddWindowT;
     private WindowDecoration.RelayoutParams mRelayoutParams = new WindowDecoration.RelayoutParams();
 
     @Before
     public void setUp() {
         mMockSurfaceControlStartT = createMockSurfaceControlTransaction();
         mMockSurfaceControlFinishT = createMockSurfaceControlTransaction();
+        mMockSurfaceControlAddWindowT = createMockSurfaceControlTransaction();
 
         doReturn(mMockSurfaceControlViewHost).when(mMockSurfaceControlViewHostFactory)
                 .create(any(), any(), any());
@@ -227,8 +231,8 @@
 
         verify(captionContainerSurfaceBuilder).setParent(decorContainerSurface);
         verify(captionContainerSurfaceBuilder).setContainerLayer();
-        verify(mMockSurfaceControlStartT).setPosition(captionContainerSurface, -46, 8);
-        verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 300, 64);
+        verify(mMockSurfaceControlStartT).setPosition(captionContainerSurface, 20, 40);
+        verify(mMockSurfaceControlStartT).setWindowCrop(captionContainerSurface, 432, 64);
         verify(mMockSurfaceControlStartT).show(captionContainerSurface);
 
         verify(mMockSurfaceControlViewHostFactory).create(any(), eq(defaultDisplay), any());
@@ -242,7 +246,7 @@
             verify(mMockView).setTaskFocusState(true);
             verify(mMockWindowContainerTransaction)
                     .addRectInsetsProvider(taskInfo.token,
-                            new Rect(100, 300, 400, 332),
+                            new Rect(100, 300, 400, 364),
                             new int[] { InsetsState.ITYPE_CAPTION_BAR });
         }
 
@@ -366,6 +370,71 @@
         verify(mMockSurfaceControlViewHost).setView(same(mMockView), any());
     }
 
+    @Test
+    public void testAddWindow() {
+        final Display defaultDisplay = mock(Display.class);
+        doReturn(defaultDisplay).when(mMockDisplayController)
+                .getDisplay(Display.DEFAULT_DISPLAY);
+
+        final SurfaceControl decorContainerSurface = mock(SurfaceControl.class);
+        final SurfaceControl.Builder decorContainerSurfaceBuilder =
+                createMockSurfaceControlBuilder(decorContainerSurface);
+        mMockSurfaceControlBuilders.add(decorContainerSurfaceBuilder);
+        final SurfaceControl taskBackgroundSurface = mock(SurfaceControl.class);
+        final SurfaceControl.Builder taskBackgroundSurfaceBuilder =
+                createMockSurfaceControlBuilder(taskBackgroundSurface);
+        mMockSurfaceControlBuilders.add(taskBackgroundSurfaceBuilder);
+        final SurfaceControl captionContainerSurface = mock(SurfaceControl.class);
+        final SurfaceControl.Builder captionContainerSurfaceBuilder =
+                createMockSurfaceControlBuilder(captionContainerSurface);
+        mMockSurfaceControlBuilders.add(captionContainerSurfaceBuilder);
+
+        final SurfaceControl.Transaction t = mock(SurfaceControl.Transaction.class);
+        mMockSurfaceControlTransactions.add(t);
+        final ActivityManager.TaskDescription.Builder taskDescriptionBuilder =
+                new ActivityManager.TaskDescription.Builder()
+                        .setBackgroundColor(Color.YELLOW);
+        final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+                .setDisplayId(Display.DEFAULT_DISPLAY)
+                .setTaskDescriptionBuilder(taskDescriptionBuilder)
+                .setBounds(TASK_BOUNDS)
+                .setPositionInParent(TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y)
+                .setVisible(true)
+                .build();
+        taskInfo.isFocused = true;
+        taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
+        mRelayoutParams.setOutsets(
+                R.dimen.test_window_decor_left_outset,
+                R.dimen.test_window_decor_top_outset,
+                R.dimen.test_window_decor_right_outset,
+                R.dimen.test_window_decor_bottom_outset);
+        final SurfaceControl taskSurface = mock(SurfaceControl.class);
+        final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo, taskSurface);
+        windowDecor.relayout(taskInfo);
+
+        final SurfaceControl additionalWindowSurface = mock(SurfaceControl.class);
+        final SurfaceControl.Builder additionalWindowSurfaceBuilder =
+                createMockSurfaceControlBuilder(additionalWindowSurface);
+        mMockSurfaceControlBuilders.add(additionalWindowSurfaceBuilder);
+
+        WindowDecoration.AdditionalWindow additionalWindow = windowDecor.addTestWindow();
+
+        verify(additionalWindowSurfaceBuilder).setContainerLayer();
+        verify(additionalWindowSurfaceBuilder).setParent(decorContainerSurface);
+        verify(additionalWindowSurfaceBuilder).build();
+        verify(mMockSurfaceControlAddWindowT).setPosition(additionalWindowSurface, 20, 40);
+        verify(mMockSurfaceControlAddWindowT).setWindowCrop(additionalWindowSurface, 432, 64);
+        verify(mMockSurfaceControlAddWindowT).show(additionalWindowSurface);
+        verify(mMockSurfaceControlViewHostFactory, Mockito.times(2))
+                .create(any(), eq(defaultDisplay), any());
+        assertThat(additionalWindow.mWindowViewHost).isNotNull();
+
+        additionalWindow.releaseView();
+
+        assertThat(additionalWindow.mWindowViewHost).isNull();
+        assertThat(additionalWindow.mWindowSurface).isNull();
+    }
+
     private TestWindowDecoration createWindowDecoration(
             ActivityManager.RunningTaskInfo taskInfo, SurfaceControl testSurface) {
         return new TestWindowDecoration(InstrumentationRegistry.getInstrumentation().getContext(),
@@ -429,5 +498,20 @@
             relayout(mRelayoutParams, mMockSurfaceControlStartT, mMockSurfaceControlFinishT,
                     mMockWindowContainerTransaction, mMockView, mRelayoutResult);
         }
+
+        private WindowDecoration.AdditionalWindow addTestWindow() {
+            final Resources resources = mDecorWindowContext.getResources();
+            int x = mRelayoutParams.mCaptionX;
+            int y = mRelayoutParams.mCaptionY;
+            int width = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionWidthId);
+            int height = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionHeightId);
+            String name = "Test Window";
+            WindowDecoration.AdditionalWindow additionalWindow =
+                    addWindow(R.layout.caption_handle_menu, name, mMockSurfaceControlAddWindowT,
+                            x - mRelayoutResult.mDecorContainerOffsetX,
+                            y - mRelayoutResult.mDecorContainerOffsetY,
+                            width, height);
+            return additionalWindow;
+        }
     }
 }
diff --git a/libs/androidfw/tests/CursorWindow_test.cpp b/libs/androidfw/tests/CursorWindow_test.cpp
index 15be80c..d1cfd03 100644
--- a/libs/androidfw/tests/CursorWindow_test.cpp
+++ b/libs/androidfw/tests/CursorWindow_test.cpp
@@ -14,6 +14,7 @@
  * limitations under the License.
  */
 
+#include <memory>
 #include <utility>
 
 #include "androidfw/CursorWindow.h"
@@ -184,7 +185,7 @@
     ASSERT_EQ(w->allocRow(), OK);
 
     // Scratch buffer that will fit before inflation
-    void* buf = malloc(kHalfInlineSize);
+    char buf[kHalfInlineSize];
 
     // Store simple value
     ASSERT_EQ(w->putLong(0, 0, 0xcafe), OK);
@@ -262,7 +263,7 @@
     ASSERT_EQ(w->allocRow(), OK);
 
     // Scratch buffer that will fit before inflation
-    void* buf = malloc(kHalfInlineSize);
+    char buf[kHalfInlineSize];
 
     // Store simple value
     ASSERT_EQ(w->putLong(0, 0, 0xcafe), OK);
@@ -322,7 +323,8 @@
     ASSERT_EQ(w->putLong(0, 0, 0xcafe), OK);
 
     // Store object that forces inflation
-    void* buf = malloc(kGiantSize);
+    std::unique_ptr<char> bufPtr(new char[kGiantSize]);
+    void* buf = bufPtr.get();
     memset(buf, 42, kGiantSize);
     ASSERT_EQ(w->putBlob(0, 1, buf, kGiantSize), OK);
 
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 29f3773..cccc0f81 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -678,6 +678,7 @@
     srcs: [
         "tests/unit/main.cpp",
         "tests/unit/ABitmapTests.cpp",
+        "tests/unit/AutoBackendTextureReleaseTests.cpp",
         "tests/unit/CacheManagerTests.cpp",
         "tests/unit/CanvasContextTests.cpp",
         "tests/unit/CanvasOpTests.cpp",
diff --git a/libs/hwui/AutoBackendTextureRelease.cpp b/libs/hwui/AutoBackendTextureRelease.cpp
index ef5eacb..b656b6a 100644
--- a/libs/hwui/AutoBackendTextureRelease.cpp
+++ b/libs/hwui/AutoBackendTextureRelease.cpp
@@ -32,9 +32,17 @@
     bool createProtectedImage = 0 != (desc.usage & AHARDWAREBUFFER_USAGE_PROTECTED_CONTENT);
     GrBackendFormat backendFormat =
             GrAHardwareBufferUtils::GetBackendFormat(context, buffer, desc.format, false);
+    LOG_ALWAYS_FATAL_IF(!backendFormat.isValid(),
+                        __FILE__ " Invalid GrBackendFormat. GrBackendApi==%" PRIu32
+                                 ", AHardwareBuffer_Format==%" PRIu32 ".",
+                        static_cast<int>(context->backend()), desc.format);
     mBackendTexture = GrAHardwareBufferUtils::MakeBackendTexture(
             context, buffer, desc.width, desc.height, &mDeleteProc, &mUpdateProc, &mImageCtx,
             createProtectedImage, backendFormat, false);
+    LOG_ALWAYS_FATAL_IF(!mBackendTexture.isValid(),
+                        __FILE__ " Invalid GrBackendTexture. Width==%" PRIu32 ", height==%" PRIu32
+                                 ", protected==%d",
+                        desc.width, desc.height, createProtectedImage);
 }
 
 void AutoBackendTextureRelease::unref(bool releaseImage) {
@@ -74,13 +82,13 @@
     AHardwareBuffer_Desc desc;
     AHardwareBuffer_describe(buffer, &desc);
     SkColorType colorType = GrAHardwareBufferUtils::GetSkColorTypeFromBufferFormat(desc.format);
+    // The following ref will be counteracted by Skia calling releaseProc, either during
+    // MakeFromTexture if there is a failure, or later when SkImage is discarded. It must
+    // be called before MakeFromTexture, otherwise Skia may remove HWUI's ref on failure.
+    ref();
     mImage = SkImage::MakeFromTexture(
             context, mBackendTexture, kTopLeft_GrSurfaceOrigin, colorType, kPremul_SkAlphaType,
             uirenderer::DataSpaceToColorSpace(dataspace), releaseProc, this);
-    if (mImage.get()) {
-        // The following ref will be counteracted by releaseProc, when SkImage is discarded.
-        ref();
-    }
 }
 
 void AutoBackendTextureRelease::newBufferContent(GrDirectContext* context) {
diff --git a/libs/hwui/AutoBackendTextureRelease.h b/libs/hwui/AutoBackendTextureRelease.h
index c9bb767..f0eb2a8 100644
--- a/libs/hwui/AutoBackendTextureRelease.h
+++ b/libs/hwui/AutoBackendTextureRelease.h
@@ -25,6 +25,9 @@
 namespace android {
 namespace uirenderer {
 
+// Friend TestUtils serves as a proxy for any test cases that require access to private members.
+class TestUtils;
+
 /**
  * AutoBackendTextureRelease manages EglImage/VkImage lifetime. It is a ref-counted object
  * that keeps GPU resources alive until the last SkImage object using them is destroyed.
@@ -66,6 +69,9 @@
 
     // mImage is the SkImage created from mBackendTexture.
     sk_sp<SkImage> mImage;
+
+    // Friend TestUtils serves as a proxy for any test cases that require access to private members.
+    friend class TestUtils;
 };
 
 } /* namespace uirenderer */
diff --git a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
index f2282e66..1a47db5 100644
--- a/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
+++ b/libs/hwui/pipeline/skia/RenderNodeDrawable.cpp
@@ -229,10 +229,10 @@
     // TODO should we let the bound of the drawable do this for us?
     const SkRect bounds = SkRect::MakeWH(properties.getWidth(), properties.getHeight());
     bool quickRejected = properties.getClipToBounds() && canvas->quickReject(bounds);
-    auto clipBounds = canvas->getLocalClipBounds();
-    SkIRect srcBounds = SkIRect::MakeWH(bounds.width(), bounds.height());
-    SkIPoint offset = SkIPoint::Make(0.0f, 0.0f);
     if (!quickRejected) {
+        auto clipBounds = canvas->getLocalClipBounds();
+        SkIRect srcBounds = SkIRect::MakeWH(bounds.width(), bounds.height());
+        SkIPoint offset = SkIPoint::Make(0.0f, 0.0f);
         SkiaDisplayList* displayList = renderNode->getDisplayList().asSkiaDl();
         const LayerProperties& layerProperties = properties.layerProperties();
         // composing a hardware layer
diff --git a/libs/hwui/renderthread/CanvasContext.cpp b/libs/hwui/renderthread/CanvasContext.cpp
index 6a0c5a8..d09bc47 100644
--- a/libs/hwui/renderthread/CanvasContext.cpp
+++ b/libs/hwui/renderthread/CanvasContext.cpp
@@ -472,11 +472,11 @@
     mRenderThread.pushBackFrameCallback(this);
 }
 
-nsecs_t CanvasContext::draw() {
+std::optional<nsecs_t> CanvasContext::draw() {
     if (auto grContext = getGrContext()) {
         if (grContext->abandoned()) {
             LOG_ALWAYS_FATAL("GrContext is abandoned/device lost at start of CanvasContext::draw");
-            return 0;
+            return std::nullopt;
         }
     }
     SkRect dirty;
@@ -498,7 +498,7 @@
             std::invoke(func, false /* didProduceBuffer */);
         }
         mFrameCommitCallbacks.clear();
-        return 0;
+        return std::nullopt;
     }
 
     ScopedActiveContext activeContext(this);
@@ -543,6 +543,8 @@
     }
 
     bool requireSwap = false;
+    bool didDraw = false;
+
     int error = OK;
     bool didSwap = mRenderPipeline->swapBuffers(frame, drawResult.success, windowDirty,
                                                 mCurrentFrameInfo, &requireSwap);
@@ -553,7 +555,7 @@
     mIsDirty = false;
 
     if (requireSwap) {
-        bool didDraw = true;
+        didDraw = true;
         // Handle any swapchain errors
         error = mNativeSurface->getAndClearError();
         if (error == TIMED_OUT) {
@@ -649,7 +651,9 @@
     }
 
     mRenderThread.cacheManager().onFrameCompleted();
-    return mCurrentFrameInfo->get(FrameInfoIndex::DequeueBufferDuration);
+    return didDraw ? std::make_optional(
+                             mCurrentFrameInfo->get(FrameInfoIndex::DequeueBufferDuration))
+                   : std::nullopt;
 }
 
 void CanvasContext::reportMetricsWithPresentTime() {
diff --git a/libs/hwui/renderthread/CanvasContext.h b/libs/hwui/renderthread/CanvasContext.h
index 748ab96..db96cfb 100644
--- a/libs/hwui/renderthread/CanvasContext.h
+++ b/libs/hwui/renderthread/CanvasContext.h
@@ -138,7 +138,7 @@
     bool makeCurrent();
     void prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t syncQueued, RenderNode* target);
     // Returns the DequeueBufferDuration.
-    nsecs_t draw();
+    std::optional<nsecs_t> draw();
     void destroy();
 
     // IFrameCallback, Choreographer-driven frame callback entry point
diff --git a/libs/hwui/renderthread/DrawFrameTask.cpp b/libs/hwui/renderthread/DrawFrameTask.cpp
index 03f02de..dc7676c 100644
--- a/libs/hwui/renderthread/DrawFrameTask.cpp
+++ b/libs/hwui/renderthread/DrawFrameTask.cpp
@@ -19,6 +19,7 @@
 #include <dlfcn.h>
 #include <gui/TraceUtils.h>
 #include <utils/Log.h>
+
 #include <algorithm>
 
 #include "../DeferredLayerUpdater.h"
@@ -28,6 +29,7 @@
 #include "CanvasContext.h"
 #include "RenderThread.h"
 #include "thread/CommonPool.h"
+#include "utils/TimeUtils.h"
 
 namespace android {
 namespace uirenderer {
@@ -146,6 +148,7 @@
 
     bool canUnblockUiThread;
     bool canDrawThisFrame;
+    bool didDraw = false;
     {
         TreeInfo info(TreeInfo::MODE_FULL, *mContext);
         info.forceDrawFrame = mForceDrawFrame;
@@ -188,7 +191,9 @@
 
     nsecs_t dequeueBufferDuration = 0;
     if (CC_LIKELY(canDrawThisFrame)) {
-        dequeueBufferDuration = context->draw();
+        std::optional<nsecs_t> drawResult = context->draw();
+        didDraw = drawResult.has_value();
+        dequeueBufferDuration = drawResult.value_or(0);
     } else {
         // Do a flush in case syncFrameState performed any texture uploads. Since we skipped
         // the draw() call, those uploads (or deletes) will end up sitting in the queue.
@@ -209,8 +214,9 @@
     }
 
     if (!mHintSessionWrapper) mHintSessionWrapper.emplace(mUiThreadId, mRenderThreadId);
-    constexpr int64_t kSanityCheckLowerBound = 100000;       // 0.1ms
-    constexpr int64_t kSanityCheckUpperBound = 10000000000;  // 10s
+
+    constexpr int64_t kSanityCheckLowerBound = 100_us;
+    constexpr int64_t kSanityCheckUpperBound = 10_s;
     int64_t targetWorkDuration = frameDeadline - intendedVsync;
     targetWorkDuration = targetWorkDuration * Properties::targetCpuTimePercentage / 100;
     if (targetWorkDuration > kSanityCheckLowerBound &&
@@ -219,12 +225,15 @@
         mLastTargetWorkDuration = targetWorkDuration;
         mHintSessionWrapper->updateTargetWorkDuration(targetWorkDuration);
     }
-    int64_t frameDuration = systemTime(SYSTEM_TIME_MONOTONIC) - frameStartTime;
-    int64_t actualDuration = frameDuration -
-                             (std::min(syncDelayDuration, mLastDequeueBufferDuration)) -
-                             dequeueBufferDuration;
-    if (actualDuration > kSanityCheckLowerBound && actualDuration < kSanityCheckUpperBound) {
-        mHintSessionWrapper->reportActualWorkDuration(actualDuration);
+
+    if (didDraw) {
+        int64_t frameDuration = systemTime(SYSTEM_TIME_MONOTONIC) - frameStartTime;
+        int64_t actualDuration = frameDuration -
+                                 (std::min(syncDelayDuration, mLastDequeueBufferDuration)) -
+                                 dequeueBufferDuration;
+        if (actualDuration > kSanityCheckLowerBound && actualDuration < kSanityCheckUpperBound) {
+            mHintSessionWrapper->reportActualWorkDuration(actualDuration);
+        }
     }
 
     mLastDequeueBufferDuration = dequeueBufferDuration;
diff --git a/libs/hwui/tests/common/TestUtils.h b/libs/hwui/tests/common/TestUtils.h
index 75865c7..9d5c13e 100644
--- a/libs/hwui/tests/common/TestUtils.h
+++ b/libs/hwui/tests/common/TestUtils.h
@@ -16,6 +16,7 @@
 
 #pragma once
 
+#include <AutoBackendTextureRelease.h>
 #include <DisplayList.h>
 #include <Matrix.h>
 #include <Properties.h>
@@ -293,6 +294,11 @@
     static SkRect getClipBounds(const SkCanvas* canvas);
     static SkRect getLocalClipBounds(const SkCanvas* canvas);
 
+    static int getUsageCount(const AutoBackendTextureRelease* textureRelease) {
+        EXPECT_NE(nullptr, textureRelease);
+        return textureRelease->mUsageCount;
+    }
+
     struct CallCounts {
         int sync = 0;
         int contextDestroyed = 0;
diff --git a/libs/hwui/tests/unit/AutoBackendTextureReleaseTests.cpp b/libs/hwui/tests/unit/AutoBackendTextureReleaseTests.cpp
new file mode 100644
index 0000000..2ec78a4
--- /dev/null
+++ b/libs/hwui/tests/unit/AutoBackendTextureReleaseTests.cpp
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <gtest/gtest.h>
+
+#include "AutoBackendTextureRelease.h"
+#include "tests/common/TestUtils.h"
+
+using namespace android;
+using namespace android::uirenderer;
+
+AHardwareBuffer* allocHardwareBuffer() {
+    AHardwareBuffer* buffer;
+    AHardwareBuffer_Desc desc = {
+            .width = 16,
+            .height = 16,
+            .layers = 1,
+            .format = AHARDWAREBUFFER_FORMAT_R8G8B8A8_UNORM,
+            .usage = AHARDWAREBUFFER_USAGE_CPU_READ_RARELY | AHARDWAREBUFFER_USAGE_CPU_WRITE_RARELY,
+    };
+    constexpr int kSucceeded = 0;
+    int status = AHardwareBuffer_allocate(&desc, &buffer);
+    EXPECT_EQ(kSucceeded, status);
+    return buffer;
+}
+
+// Expands to AutoBackendTextureRelease_makeImage_invalid_RenderThreadTest,
+// set as friend in AutoBackendTextureRelease.h
+RENDERTHREAD_TEST(AutoBackendTextureRelease, makeImage_invalid) {
+    AHardwareBuffer* buffer = allocHardwareBuffer();
+    AutoBackendTextureRelease* textureRelease =
+            new AutoBackendTextureRelease(renderThread.getGrContext(), buffer);
+
+    EXPECT_EQ(1, TestUtils::getUsageCount(textureRelease));
+
+    // SkImage::MakeFromTexture should fail if given null GrDirectContext.
+    textureRelease->makeImage(buffer, HAL_DATASPACE_UNKNOWN, /*context = */ nullptr);
+
+    EXPECT_EQ(1, TestUtils::getUsageCount(textureRelease));
+
+    textureRelease->unref(true);
+    AHardwareBuffer_release(buffer);
+}
+
+// Expands to AutoBackendTextureRelease_makeImage_valid_RenderThreadTest,
+// set as friend in AutoBackendTextureRelease.h
+RENDERTHREAD_TEST(AutoBackendTextureRelease, makeImage_valid) {
+    AHardwareBuffer* buffer = allocHardwareBuffer();
+    AutoBackendTextureRelease* textureRelease =
+            new AutoBackendTextureRelease(renderThread.getGrContext(), buffer);
+
+    EXPECT_EQ(1, TestUtils::getUsageCount(textureRelease));
+
+    textureRelease->makeImage(buffer, HAL_DATASPACE_UNKNOWN, renderThread.getGrContext());
+
+    EXPECT_EQ(2, TestUtils::getUsageCount(textureRelease));
+
+    textureRelease->unref(true);
+    AHardwareBuffer_release(buffer);
+}
diff --git a/location/java/android/location/GnssCapabilities.java b/location/java/android/location/GnssCapabilities.java
index b38f9ea..98578b2 100644
--- a/location/java/android/location/GnssCapabilities.java
+++ b/location/java/android/location/GnssCapabilities.java
@@ -24,6 +24,8 @@
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
 import java.util.Objects;
 import java.util.concurrent.Executor;
 
@@ -123,20 +125,23 @@
      * @hide
      */
     public static GnssCapabilities empty() {
-        return new GnssCapabilities(0, 0, 0);
+        return new GnssCapabilities(0, 0, 0, new ArrayList<>());
     }
 
     private final @TopHalCapabilityFlags int mTopFlags;
     private final @SubHalMeasurementCorrectionsCapabilityFlags int mMeasurementCorrectionsFlags;
     private final @SubHalPowerCapabilityFlags int mPowerFlags;
+    private final @NonNull List<GnssSignalType> mGnssSignalTypes;
 
     private GnssCapabilities(
             @TopHalCapabilityFlags int topFlags,
             @SubHalMeasurementCorrectionsCapabilityFlags int measurementCorrectionsFlags,
-            @SubHalPowerCapabilityFlags int powerFlags) {
+            @SubHalPowerCapabilityFlags int powerFlags,
+            @NonNull List<GnssSignalType> gnssSignalTypes) {
         mTopFlags = topFlags;
         mMeasurementCorrectionsFlags = measurementCorrectionsFlags;
         mPowerFlags = powerFlags;
+        mGnssSignalTypes = gnssSignalTypes;
     }
 
     /**
@@ -148,7 +153,8 @@
         if (mTopFlags == flags) {
             return this;
         } else {
-            return new GnssCapabilities(flags, mMeasurementCorrectionsFlags, mPowerFlags);
+            return new GnssCapabilities(flags, mMeasurementCorrectionsFlags, mPowerFlags,
+                    new ArrayList<>(mGnssSignalTypes));
         }
     }
 
@@ -163,7 +169,8 @@
         if (mMeasurementCorrectionsFlags == flags) {
             return this;
         } else {
-            return new GnssCapabilities(mTopFlags, flags, mPowerFlags);
+            return new GnssCapabilities(mTopFlags, flags, mPowerFlags,
+                    new ArrayList<>(mGnssSignalTypes));
         }
     }
 
@@ -177,7 +184,8 @@
         if (mPowerFlags == flags) {
             return this;
         } else {
-            return new GnssCapabilities(mTopFlags, mMeasurementCorrectionsFlags, flags);
+            return new GnssCapabilities(mTopFlags, mMeasurementCorrectionsFlags, flags,
+                    new ArrayList<>(mGnssSignalTypes));
         }
     }
 
@@ -424,6 +432,14 @@
         return (mPowerFlags & SUB_HAL_POWER_CAPABILITY_OTHER_MODES) != 0;
     }
 
+    /**
+     * Returns the list of {@link GnssSignalType}s that the GNSS chipset supports.
+     */
+    @NonNull
+    public List<GnssSignalType> getGnssSignalTypes() {
+        return mGnssSignalTypes;
+    }
+
     @Override
     public boolean equals(Object o) {
         if (this == o) {
@@ -441,14 +457,15 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mTopFlags, mMeasurementCorrectionsFlags, mPowerFlags);
+        return Objects.hash(mTopFlags, mMeasurementCorrectionsFlags, mPowerFlags, mGnssSignalTypes);
     }
 
     public static final @NonNull Creator<GnssCapabilities> CREATOR =
             new Creator<GnssCapabilities>() {
                 @Override
                 public GnssCapabilities createFromParcel(Parcel in) {
-                    return new GnssCapabilities(in.readInt(), in.readInt(), in.readInt());
+                    return new GnssCapabilities(in.readInt(), in.readInt(), in.readInt(),
+                            in.createTypedArrayList(GnssSignalType.CREATOR));
                 }
 
                 @Override
@@ -467,6 +484,7 @@
         parcel.writeInt(mTopFlags);
         parcel.writeInt(mMeasurementCorrectionsFlags);
         parcel.writeInt(mPowerFlags);
+        parcel.writeTypedList(mGnssSignalTypes);
     }
 
     @Override
@@ -545,6 +563,9 @@
         if (hasPowerOtherModes()) {
             builder.append("OTHER_MODES_POWER ");
         }
+        if (!mGnssSignalTypes.isEmpty()) {
+            builder.append("signalTypes=").append(mGnssSignalTypes).append(" ");
+        }
         if (builder.length() > 1) {
             builder.setLength(builder.length() - 1);
         } else {
@@ -562,17 +583,20 @@
         private @TopHalCapabilityFlags int mTopFlags;
         private @SubHalMeasurementCorrectionsCapabilityFlags int mMeasurementCorrectionsFlags;
         private @SubHalPowerCapabilityFlags int mPowerFlags;
+        private @NonNull List<GnssSignalType> mGnssSignalTypes;
 
         public Builder() {
             mTopFlags = 0;
             mMeasurementCorrectionsFlags = 0;
             mPowerFlags = 0;
+            mGnssSignalTypes = new ArrayList<>();
         }
 
         public Builder(@NonNull GnssCapabilities capabilities) {
             mTopFlags = capabilities.mTopFlags;
             mMeasurementCorrectionsFlags = capabilities.mMeasurementCorrectionsFlags;
             mPowerFlags = capabilities.mPowerFlags;
+            mGnssSignalTypes = capabilities.mGnssSignalTypes;
         }
 
         /**
@@ -779,10 +803,19 @@
         }
 
         /**
+         * Sets a list of {@link GnssSignalType}.
+         */
+        public @NonNull Builder setGnssSignalTypes(@NonNull List<GnssSignalType> gnssSignalTypes) {
+            mGnssSignalTypes = gnssSignalTypes;
+            return this;
+        }
+
+        /**
          * Builds a new GnssCapabilities.
          */
         public @NonNull GnssCapabilities build() {
-            return new GnssCapabilities(mTopFlags, mMeasurementCorrectionsFlags, mPowerFlags);
+            return new GnssCapabilities(mTopFlags, mMeasurementCorrectionsFlags, mPowerFlags,
+                    new ArrayList<>(mGnssSignalTypes));
         }
 
         private static int setFlag(int value, int flag, boolean set) {
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl b/location/java/android/location/GnssSignalType.aidl
similarity index 73%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
copy to location/java/android/location/GnssSignalType.aidl
index 1550ab3..1c43fe5 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
+++ b/location/java/android/location/GnssSignalType.aidl
@@ -1,11 +1,11 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2022, 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
+ *     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,
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package com.android.systemui.shared.system;
+package android.location;
 
-parcelable RemoteTransitionCompat;
+parcelable GnssSignalType;
diff --git a/location/java/android/location/GnssSignalType.java b/location/java/android/location/GnssSignalType.java
new file mode 100644
index 0000000..f9c6e72
--- /dev/null
+++ b/location/java/android/location/GnssSignalType.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.location;
+
+import android.annotation.FloatRange;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.util.Preconditions;
+
+import java.util.Objects;
+
+/**
+ * This class represents a GNSS signal type.
+ */
+public final class GnssSignalType implements Parcelable {
+
+    /**
+     * Creates a {@link GnssSignalType} with a full list of parameters.
+     *
+     * @param constellationType the constellation type as defined in
+     * {@link GnssStatus.ConstellationType}
+     * @param carrierFrequencyHz the carrier frequency in Hz
+     * @param codeType the code type as defined in {@link GnssMeasurement#getCodeType()}
+     */
+    @NonNull
+    public static GnssSignalType create(@GnssStatus.ConstellationType int constellationType,
+            @FloatRange(from = 0.0f, fromInclusive = false) double carrierFrequencyHz,
+            @NonNull String codeType) {
+        Preconditions.checkArgument(carrierFrequencyHz > 0,
+                "carrierFrequencyHz must be greater than 0.");
+        Objects.requireNonNull(codeType);
+        return new GnssSignalType(constellationType, carrierFrequencyHz, codeType);
+    }
+
+    @GnssStatus.ConstellationType
+    private final int mConstellationType;
+    @FloatRange(from = 0.0f, fromInclusive = false)
+    private final double mCarrierFrequencyHz;
+    @NonNull
+    private final String mCodeType;
+
+    /**
+     * Creates a {@link GnssSignalType} with a full list of parameters.
+     */
+    private GnssSignalType(@GnssStatus.ConstellationType int constellationType,
+            double carrierFrequencyHz, @NonNull String codeType) {
+        this.mConstellationType = constellationType;
+        this.mCarrierFrequencyHz = carrierFrequencyHz;
+        this.mCodeType = codeType;
+    }
+
+    /** Returns the {@link GnssStatus.ConstellationType}. */
+    @GnssStatus.ConstellationType
+    public int getConstellationType() {
+        return mConstellationType;
+    }
+
+    /** Returns the carrier frequency in Hz. */
+    @FloatRange(from = 0.0f, fromInclusive = false)
+    public double getCarrierFrequencyHz() {
+        return mCarrierFrequencyHz;
+    }
+
+    /**
+     * Return the code type.
+     *
+     * @see GnssMeasurement#getCodeType()
+     */
+    @NonNull
+    public String getCodeType() {
+        return mCodeType;
+    }
+
+    @NonNull
+    public static final Parcelable.Creator<GnssSignalType> CREATOR =
+            new Parcelable.Creator<GnssSignalType>() {
+                @Override
+                @NonNull
+                public GnssSignalType createFromParcel(@NonNull Parcel parcel) {
+                    return new GnssSignalType(parcel.readInt(), parcel.readDouble(),
+                            parcel.readString());
+                }
+
+                @Override
+                public GnssSignalType[] newArray(int i) {
+                    return new GnssSignalType[i];
+                }
+            };
+
+    @Override
+    public void writeToParcel(@NonNull Parcel parcel, int flags) {
+        parcel.writeInt(mConstellationType);
+        parcel.writeDouble(mCarrierFrequencyHz);
+        parcel.writeString(mCodeType);
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        StringBuilder s = new StringBuilder();
+        s.append("GnssSignalType[");
+        s.append(" Constellation=").append(mConstellationType);
+        s.append(", CarrierFrequencyHz=").append(mCarrierFrequencyHz);
+        s.append(", CodeType=").append(mCodeType);
+        s.append(']');
+        return s.toString();
+    }
+
+    @Override
+    public boolean equals(@Nullable Object obj) {
+        if (this == obj) {
+            return true;
+        }
+        if (obj == null) {
+            return false;
+        }
+        if (obj instanceof GnssSignalType) {
+            GnssSignalType other = (GnssSignalType) obj;
+            return mConstellationType == other.mConstellationType
+                    && Double.compare(mCarrierFrequencyHz, other.mCarrierFrequencyHz) == 0
+                    && mCodeType.equals(other.mCodeType);
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mConstellationType, mCarrierFrequencyHz, mCodeType);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+}
diff --git a/media/java/android/media/AudioPlaybackConfiguration.java b/media/java/android/media/AudioPlaybackConfiguration.java
index 819358b..980f63b 100644
--- a/media/java/android/media/AudioPlaybackConfiguration.java
+++ b/media/java/android/media/AudioPlaybackConfiguration.java
@@ -23,6 +23,7 @@
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.SystemApi;
 import android.os.Binder;
 import android.os.IBinder;
@@ -220,46 +221,59 @@
 
     /**
      * @hide
-     * Mute state used for anonymization.
+     * Mute state used for the initial state and when API is accessed without permission.
      */
-    public static final int PLAYER_MUTE_INVALID = -1;
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public static final int MUTED_BY_UNKNOWN = -1;
     /**
      * @hide
      * Flag used when muted by master volume.
      */
-    public static final int PLAYER_MUTE_MASTER = (1 << 0);
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public static final int MUTED_BY_MASTER = (1 << 0);
     /**
      * @hide
      * Flag used when muted by stream volume.
      */
-    public static final int PLAYER_MUTE_STREAM_VOLUME = (1 << 1);
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public static final int MUTED_BY_STREAM_VOLUME = (1 << 1);
     /**
      * @hide
      * Flag used when muted by stream mute.
      */
-    public static final int PLAYER_MUTE_STREAM_MUTED = (1 << 2);
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public static final int MUTED_BY_STREAM_MUTED = (1 << 2);
     /**
      * @hide
-     * Flag used when playback is restricted by AppOps manager with OP_PLAY_AUDIO.
+     * Flag used when playback is muted by AppOpsManager#OP_PLAY_AUDIO.
      */
-    public static final int PLAYER_MUTE_PLAYBACK_RESTRICTED = (1 << 3);
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public static final int MUTED_BY_APP_OPS = (1 << 3);
     /**
      * @hide
      * Flag used when muted by client volume.
      */
-    public static final int PLAYER_MUTE_CLIENT_VOLUME = (1 << 4);
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public static final int MUTED_BY_CLIENT_VOLUME = (1 << 4);
     /**
      * @hide
      * Flag used when muted by volume shaper.
      */
-    public static final int PLAYER_MUTE_VOLUME_SHAPER = (1 << 5);
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public static final int MUTED_BY_VOLUME_SHAPER = (1 << 5);
 
     /** @hide */
     @IntDef(
             flag = true,
-            value = {PLAYER_MUTE_MASTER, PLAYER_MUTE_STREAM_VOLUME, PLAYER_MUTE_STREAM_MUTED,
-                    PLAYER_MUTE_PLAYBACK_RESTRICTED, PLAYER_MUTE_CLIENT_VOLUME,
-                    PLAYER_MUTE_VOLUME_SHAPER})
+            value = {MUTED_BY_MASTER, MUTED_BY_STREAM_VOLUME, MUTED_BY_STREAM_MUTED,
+                    MUTED_BY_APP_OPS, MUTED_BY_CLIENT_VOLUME, MUTED_BY_VOLUME_SHAPER})
     @Retention(RetentionPolicy.SOURCE)
     public @interface PlayerMuteEvent {
     }
@@ -303,7 +317,7 @@
         mPlayerType = pic.mPlayerType;
         mClientUid = uid;
         mClientPid = pid;
-        mMutedState = PLAYER_MUTE_INVALID;
+        mMutedState = MUTED_BY_UNKNOWN;
         mDeviceId = PLAYER_DEVICEID_INVALID;
         mPlayerState = PLAYER_STATE_IDLE;
         mPlayerAttr = pic.mAttributes;
@@ -352,7 +366,7 @@
         anonymCopy.mPlayerAttr = builder.build();
         anonymCopy.mDeviceId = in.mDeviceId;
         // anonymized data
-        anonymCopy.mMutedState = PLAYER_MUTE_INVALID;
+        anonymCopy.mMutedState = MUTED_BY_UNKNOWN;
         anonymCopy.mPlayerType = PLAYER_TYPE_UNKNOWN;
         anonymCopy.mClientUid = PLAYER_UPID_INVALID;
         anonymCopy.mClientPid = PLAYER_UPID_INVALID;
@@ -413,9 +427,27 @@
 
     /**
      * @hide
-     * @return the mute state as a combination of {@link PlayerMuteEvent} flags
+     * Used for determining if the current player is muted.
+     * <br>Note that if this result is true then {@link #getMutedBy} will be > 0.
+     * @return {@code true} if the player associated with this configuration has been muted (by any
+     * given MUTED_BY_* source event) or {@code false} otherwise.
      */
-    @PlayerMuteEvent public int getMutedState() {
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    public boolean isMuted() {
+        return mMutedState != 0 && mMutedState != MUTED_BY_UNKNOWN;
+    }
+
+    /**
+     * @hide
+     * Returns a bitmask expressing the mute state as a combination of MUTED_BY_* flags.
+     * <br>Note that if the mute state is not set the result will be {@link #MUTED_BY_UNKNOWN}. A
+     * value of 0 represents a player which is not muted.
+     * @return the mute state.
+     */
+    @SystemApi
+    @RequiresPermission(android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+    @PlayerMuteEvent public int getMutedBy() {
         return mMutedState;
     }
 
@@ -569,6 +601,17 @@
         }
     }
 
+    private boolean isMuteAffectingActiveState() {
+        if (mMutedState == MUTED_BY_UNKNOWN) {
+            // mute state is not set, therefore it will not affect the active state
+            return false;
+        }
+
+        return (mMutedState & MUTED_BY_CLIENT_VOLUME) != 0
+                || (mMutedState & MUTED_BY_VOLUME_SHAPER) != 0
+                || (mMutedState & MUTED_BY_APP_OPS) != 0;
+    }
+
     /**
      * @hide
      * Returns true if the player is considered "active", i.e. actively playing with unmuted
@@ -580,8 +623,7 @@
     public boolean isActive() {
         switch (mPlayerState) {
             case PLAYER_STATE_STARTED:
-                return mMutedState == 0
-                        || mMutedState == PLAYER_MUTE_INVALID;  // only send true if not muted
+                return !isMuteAffectingActiveState();
             case PLAYER_STATE_UNKNOWN:
             case PLAYER_STATE_RELEASED:
             case PLAYER_STATE_IDLE:
@@ -684,27 +726,27 @@
                 "/").append(mClientPid).append(" state:").append(
                 toLogFriendlyPlayerState(mPlayerState)).append(" attr:").append(mPlayerAttr).append(
                 " sessionId:").append(mSessionId).append(" mutedState:");
-        if (mMutedState == PLAYER_MUTE_INVALID) {
-            apcToString.append("invalid ");
+        if (mMutedState == MUTED_BY_UNKNOWN) {
+            apcToString.append("unknown ");
         } else if (mMutedState == 0) {
             apcToString.append("none ");
         } else {
-            if ((mMutedState & PLAYER_MUTE_MASTER) != 0) {
+            if ((mMutedState & MUTED_BY_MASTER) != 0) {
                 apcToString.append("master ");
             }
-            if ((mMutedState & PLAYER_MUTE_STREAM_VOLUME) != 0) {
+            if ((mMutedState & MUTED_BY_STREAM_VOLUME) != 0) {
                 apcToString.append("streamVolume ");
             }
-            if ((mMutedState & PLAYER_MUTE_STREAM_MUTED) != 0) {
+            if ((mMutedState & MUTED_BY_STREAM_MUTED) != 0) {
                 apcToString.append("streamMute ");
             }
-            if ((mMutedState & PLAYER_MUTE_PLAYBACK_RESTRICTED) != 0) {
-                apcToString.append("playbackRestricted ");
+            if ((mMutedState & MUTED_BY_APP_OPS) != 0) {
+                apcToString.append("appOps ");
             }
-            if ((mMutedState & PLAYER_MUTE_CLIENT_VOLUME) != 0) {
+            if ((mMutedState & MUTED_BY_CLIENT_VOLUME) != 0) {
                 apcToString.append("clientVolume ");
             }
-            if ((mMutedState & PLAYER_MUTE_VOLUME_SHAPER) != 0) {
+            if ((mMutedState & MUTED_BY_VOLUME_SHAPER) != 0) {
                 apcToString.append("volumeShaper ");
             }
         }
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index 26cb9f8..b4b908d 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -104,8 +104,8 @@
     private final String mPackageName;
 
     /**
-     * Stores the latest copy of all routes received from {@link MediaRouter2ServiceImpl}, without
-     * any filtering, sorting, or deduplication.
+     * Stores the latest copy of all routes received from the system server, without any filtering,
+     * sorting, or deduplication.
      *
      * <p>Uses {@link MediaRoute2Info#getId()} to set each entry's key.
      */
diff --git a/media/jni/android_media_MediaCodec.cpp b/media/jni/android_media_MediaCodec.cpp
index 95599bd..1183ca3 100644
--- a/media/jni/android_media_MediaCodec.cpp
+++ b/media/jni/android_media_MediaCodec.cpp
@@ -174,6 +174,12 @@
     jfieldID typeId;
 } gDescriptorInfo;
 
+static struct {
+    jclass clazz;
+    jmethodID ctorId;
+    jmethodID setId;
+} gBufferInfo;
+
 struct fields_t {
     jmethodID postEventFromNativeID;
     jmethodID lockAndGetContextID;
@@ -460,11 +466,7 @@
         return err;
     }
 
-    ScopedLocalRef<jclass> clazz(
-            env, env->FindClass("android/media/MediaCodec$BufferInfo"));
-
-    jmethodID method = env->GetMethodID(clazz.get(), "set", "(IIJI)V");
-    env->CallVoidMethod(bufferInfo, method, (jint)offset, (jint)size, timeUs, flags);
+    env->CallVoidMethod(bufferInfo, gBufferInfo.setId, (jint)offset, (jint)size, timeUs, flags);
 
     return OK;
 }
@@ -1091,13 +1093,7 @@
             CHECK(msg->findInt64("timeUs", &timeUs));
             CHECK(msg->findInt32("flags", (int32_t *)&flags));
 
-            ScopedLocalRef<jclass> clazz(
-                    env, env->FindClass("android/media/MediaCodec$BufferInfo"));
-            jmethodID ctor = env->GetMethodID(clazz.get(), "<init>", "()V");
-            jmethodID method = env->GetMethodID(clazz.get(), "set", "(IIJI)V");
-
-            obj = env->NewObject(clazz.get(), ctor);
-
+            obj = env->NewObject(gBufferInfo.clazz, gBufferInfo.ctorId);
             if (obj == NULL) {
                 if (env->ExceptionCheck()) {
                     ALOGE("Could not create MediaCodec.BufferInfo.");
@@ -1107,7 +1103,7 @@
                 return;
             }
 
-            env->CallVoidMethod(obj, method, (jint)offset, (jint)size, timeUs, flags);
+            env->CallVoidMethod(obj, gBufferInfo.setId, (jint)offset, (jint)size, timeUs, flags);
             break;
         }
 
@@ -3235,6 +3231,16 @@
 
     gDescriptorInfo.typeId = env->GetFieldID(clazz.get(), "mType", "I");
     CHECK(gDescriptorInfo.typeId != NULL);
+
+    clazz.reset(env->FindClass("android/media/MediaCodec$BufferInfo"));
+    CHECK(clazz.get() != NULL);
+    gBufferInfo.clazz = (jclass)env->NewGlobalRef(clazz.get());
+
+    gBufferInfo.ctorId = env->GetMethodID(clazz.get(), "<init>", "()V");
+    CHECK(gBufferInfo.ctorId != NULL);
+
+    gBufferInfo.setId = env->GetMethodID(clazz.get(), "set", "(IIJI)V");
+    CHECK(gBufferInfo.setId != NULL);
 }
 
 static void android_media_MediaCodec_native_setup(
diff --git a/packages/CarrierDefaultApp/Android.bp b/packages/CarrierDefaultApp/Android.bp
index 6990ad0..62ffe38 100644
--- a/packages/CarrierDefaultApp/Android.bp
+++ b/packages/CarrierDefaultApp/Android.bp
@@ -13,4 +13,9 @@
     libs: ["SliceStore"],
     platform_apis: true,
     certificate: "platform",
+    optimize: {
+        proguard_flags_files: [
+            "proguard.flags",
+        ],
+    },
 }
diff --git a/packages/CarrierDefaultApp/assets/slice_store_test.html b/packages/CarrierDefaultApp/assets/slice_store_test.html
new file mode 100644
index 0000000..7ddbd2d
--- /dev/null
+++ b/packages/CarrierDefaultApp/assets/slice_store_test.html
@@ -0,0 +1,78 @@
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+
+<!DOCTYPE html>
+<html lang="en">
+<head>
+    <meta charset="UTF-8">
+    <meta name="description" content="
+    This is a HTML page that calls and verifies responses from the @JavascriptInterface functions of
+    SliceStoreWebInterface. Test SliceStore APIs using ADB shell commands and the APIs below:
+
+    FROM TERMINAL:
+    Allow device to override carrier configs:
+    $ adb root
+    Set PREMIUM_CAPABILITY_PRIORITIZE_LATENCY enabled:
+    $ adb shell cmd phone cc set-value -p supported_premium_capabilities_int_array 34
+    Set the carrier purchase URL to this test HTML file:
+    $ adb shell cmd phone cc set-value -p premium_capability_purchase_url_string \
+      file:///android_asset/slice_store_test.html
+    OPTIONAL: Allow premium capability purchase on LTE:
+    $ adb shell cmd phone cc set-value -p premium_capability_supported_on_lte_bool true
+    OPTIONAL: Override ServiceState to fake a NR SA connection:
+    $ adb shell am broadcast -a com.android.internal.telephony.TestServiceState --ei data_rat 20
+
+    FROM TEST ACTIVITY:
+    TelephonyManager tm = getApplicationContext().getSystemService(TelephonyManager.class)
+    tm.isPremiumCapabilityAvailable(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY);
+    LinkedBlockingQueue<Integer> purchaseRequests = new LinkedBlockingQueue<>();
+    tm.purchasePremiumCapability(TelephonyManager.PREMIUM_CAPABILITY_PRIORITIZE_LATENCY,
+            this.getMainExecutor(), request::offer);
+
+    When the test application starts, this HTML will be loaded into the WebView along with the
+    associated JavaScript functions in file:///android_asset/slice_store_test.js.
+    Click on the buttons in the HTML to call the corresponding @JavascriptInterface APIs.
+
+    RESET DEVICE STATE:
+    Clear carrier configurations that were set:
+    $ adb shell cmd phone cc clear-values
+    Clear ServiceState override that was set:
+    $ adb shell am broadcast -a com.android.internal.telephony.TestServiceState --es action reset
+    ">
+    <title>Test SliceStoreActivity</title>
+    <script type="text/javascript" src="slice_store_test.js"></script>
+</head>
+<body>
+    <h1>Test SliceStoreActivity</h1>
+    <h2>Get requested premium capability</h2>
+    <button type="button" onclick="testGetRequestedCapability()">
+        Get requested premium capability
+    </button>
+    <p id="requested_capability"></p>
+
+    <h2>Notify purchase successful</h2>
+    <button type="button" onclick="testNotifyPurchaseSuccessful(60000)">
+        Notify purchase successful for 1 minute
+    </button>
+    <p id="purchase_successful"></p>
+
+    <h2>Notify purchase failed</h2>
+    <button type="button" onclick="testNotifyPurchaseFailed()">
+        Notify purchase failed
+    </button>
+    <p id="purchase_failed"></p>
+</body>
+</html>
diff --git a/packages/CarrierDefaultApp/assets/slice_store_test.js b/packages/CarrierDefaultApp/assets/slice_store_test.js
new file mode 100644
index 0000000..f12a6da
--- /dev/null
+++ b/packages/CarrierDefaultApp/assets/slice_store_test.js
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+function testGetRequestedCapability() {
+    let capability = SliceStoreWebInterface.getRequestedCapability();
+    document.getElementById("requested_capability").innerHTML =
+            "Premium capability requested: " + capability;
+}
+
+function testNotifyPurchaseSuccessful(duration_ms_long = 0) {
+    SliceStoreWebInterface.notifyPurchaseSuccessful(duration);
+    document.getElementById("purchase_successful").innerHTML =
+            "Notified purchase success for duration: " + duration;
+}
+
+function testNotifyPurchaseFailed() {
+    SliceStoreWebInterface.notifyPurchaseFailed();
+    document.getElementById("purchase_failed").innerHTML =
+            "Notified purchase failed.";
+}
diff --git a/packages/CarrierDefaultApp/proguard.flags b/packages/CarrierDefaultApp/proguard.flags
new file mode 100644
index 0000000..64fec2c
--- /dev/null
+++ b/packages/CarrierDefaultApp/proguard.flags
@@ -0,0 +1,4 @@
+# Keep classes and methods that have the @JavascriptInterface annotation
+-keepclassmembers class * {
+    @android.webkit.JavascriptInterface <methods>;
+}
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreActivity.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreActivity.java
index 24cb5f9..348e389 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreActivity.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreActivity.java
@@ -20,47 +20,63 @@
 import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.NotificationManager;
+import android.content.Context;
 import android.content.Intent;
 import android.os.Bundle;
 import android.telephony.CarrierConfigManager;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.util.Log;
+import android.view.KeyEvent;
 import android.webkit.WebView;
 
 import com.android.phone.slicestore.SliceStore;
 
 import java.net.MalformedURLException;
 import java.net.URL;
+import java.util.concurrent.TimeUnit;
 
 /**
  * Activity that launches when the user clicks on the network boost notification.
+ * This will open a {@link WebView} for the carrier website to allow the user to complete the
+ * premium capability purchase.
+ * The carrier website can get the requested premium capability using the JavaScript interface
+ * method {@code SliceStoreWebInterface.getRequestedCapability()}.
+ * If the purchase is successful, the carrier website shall notify SliceStore using the JavaScript
+ * interface method {@code SliceStoreWebInterface.notifyPurchaseSuccessful(duration)}, where
+ * {@code duration} is the duration of the network boost.
+ * If the purchase was not successful, the carrier website shall notify SliceStore using the
+ * JavaScript interface method {@code SliceStoreWebInterface.notifyPurchaseFailed()}.
+ * If either of these notification methods are not called, the purchase cannot be completed
+ * successfully and the purchase request will eventually time out.
  */
 public class SliceStoreActivity extends Activity {
     private static final String TAG = "SliceStoreActivity";
 
-    private URL mUrl;
-    private WebView mWebView;
-    private int mPhoneId;
+    private @NonNull WebView mWebView;
+    private @NonNull Context mApplicationContext;
     private int mSubId;
-    private @TelephonyManager.PremiumCapability int mCapability;
+    @TelephonyManager.PremiumCapability protected int mCapability;
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         Intent intent = getIntent();
-        mPhoneId = intent.getIntExtra(SliceStore.EXTRA_PHONE_ID,
-                SubscriptionManager.INVALID_PHONE_INDEX);
         mSubId = intent.getIntExtra(SliceStore.EXTRA_SUB_ID,
                 SubscriptionManager.INVALID_SUBSCRIPTION_ID);
         mCapability = intent.getIntExtra(SliceStore.EXTRA_PREMIUM_CAPABILITY,
                 SliceStore.PREMIUM_CAPABILITY_INVALID);
-        mUrl = getUrl();
-        logd("onCreate: mPhoneId=" + mPhoneId + ", mSubId=" + mSubId + ", mCapability="
+        mApplicationContext = getApplicationContext();
+        URL url = getUrl();
+        logd("onCreate: subId=" + mSubId + ", capability="
                 + TelephonyManager.convertPremiumCapabilityToString(mCapability)
-                + ", mUrl=" + mUrl);
-        getApplicationContext().getSystemService(NotificationManager.class)
+                + ", url=" + url);
+
+        // Cancel network boost notification
+        mApplicationContext.getSystemService(NotificationManager.class)
                 .cancel(SliceStoreBroadcastReceiver.NETWORK_BOOST_NOTIFICATION_TAG, mCapability);
+
+        // Verify intent and values are valid
         if (!SliceStoreBroadcastReceiver.isIntentValid(intent)) {
             loge("Not starting SliceStoreActivity with an invalid Intent: " + intent);
             SliceStoreBroadcastReceiver.sendSliceStoreResponse(
@@ -68,10 +84,15 @@
             finishAndRemoveTask();
             return;
         }
-        if (mUrl == null) {
-            loge("Unable to create a URL from carrier configs.");
-            SliceStoreBroadcastReceiver.sendSliceStoreResponse(
-                    intent, SliceStore.EXTRA_INTENT_CARRIER_ERROR);
+        if (url == null) {
+            String error = "Unable to create a URL from carrier configs.";
+            loge(error);
+            Intent data = new Intent();
+            data.putExtra(SliceStore.EXTRA_FAILURE_CODE,
+                    SliceStore.FAILURE_CODE_CARRIER_URL_UNAVAILABLE);
+            data.putExtra(SliceStore.EXTRA_FAILURE_REASON, error);
+            SliceStoreBroadcastReceiver.sendSliceStoreResponseWithData(
+                    mApplicationContext, getIntent(), SliceStore.EXTRA_INTENT_CARRIER_ERROR, data);
             finishAndRemoveTask();
             return;
         }
@@ -83,12 +104,53 @@
             return;
         }
 
+        // Create a reference to this activity in SliceStoreBroadcastReceiver
         SliceStoreBroadcastReceiver.updateSliceStoreActivity(mCapability, this);
 
-        mWebView = new WebView(getApplicationContext());
+        // Create and configure WebView
+        mWebView = new WebView(this);
+        // Enable JavaScript for the carrier purchase website to send results back to SliceStore
+        mWebView.getSettings().setJavaScriptEnabled(true);
+        mWebView.addJavascriptInterface(new SliceStoreWebInterface(this), "SliceStoreWebInterface");
+
+        // Display WebView
         setContentView(mWebView);
-        mWebView.loadUrl(mUrl.toString());
-        // TODO(b/245882601): Get back response from WebView
+        mWebView.loadUrl(url.toString());
+    }
+
+    protected void onPurchaseSuccessful(long duration) {
+        logd("onPurchaseSuccessful: Carrier website indicated successfully purchased premium "
+                + "capability " + TelephonyManager.convertPremiumCapabilityToString(mCapability)
+                + " for " + TimeUnit.MILLISECONDS.toMinutes(duration) + " minutes.");
+        Intent intent = new Intent();
+        intent.putExtra(SliceStore.EXTRA_PURCHASE_DURATION, duration);
+        SliceStoreBroadcastReceiver.sendSliceStoreResponseWithData(
+                mApplicationContext, getIntent(), SliceStore.EXTRA_INTENT_SUCCESS, intent);
+        finishAndRemoveTask();
+    }
+
+    protected void onPurchaseFailed(@SliceStore.FailureCode int failureCode,
+            @Nullable String failureReason) {
+        logd("onPurchaseFailed: Carrier website indicated purchase failed for premium capability "
+                + TelephonyManager.convertPremiumCapabilityToString(mCapability) + " with code: "
+                + SliceStore.convertFailureCodeToString(failureCode) + " and reason: "
+                + failureReason);
+        Intent data = new Intent();
+        data.putExtra(SliceStore.EXTRA_FAILURE_CODE, failureCode);
+        data.putExtra(SliceStore.EXTRA_FAILURE_REASON, failureReason);
+        SliceStoreBroadcastReceiver.sendSliceStoreResponseWithData(
+                mApplicationContext, getIntent(), SliceStore.EXTRA_INTENT_CARRIER_ERROR, data);
+        finishAndRemoveTask();
+    }
+
+    @Override
+    public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
+        // Pressing back in the WebView will go to the previous page instead of closing SliceStore.
+        if ((keyCode == KeyEvent.KEYCODE_BACK) && mWebView.canGoBack()) {
+            mWebView.goBack();
+            return true;
+        }
+        return super.onKeyDown(keyCode, event);
     }
 
     @Override
@@ -100,8 +162,8 @@
         super.onDestroy();
     }
 
-    private @Nullable URL getUrl() {
-        String url = getApplicationContext().getSystemService(CarrierConfigManager.class)
+    @Nullable private URL getUrl() {
+        String url = mApplicationContext.getSystemService(CarrierConfigManager.class)
                 .getConfigForSubId(mSubId).getString(
                         CarrierConfigManager.KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING);
         try {
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreBroadcastReceiver.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreBroadcastReceiver.java
index 7eb851d..7867ef1 100644
--- a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreBroadcastReceiver.java
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreBroadcastReceiver.java
@@ -26,6 +26,7 @@
 import android.content.Intent;
 import android.graphics.drawable.Icon;
 import android.os.UserHandle;
+import android.telephony.AnomalyReporter;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.text.TextUtils;
@@ -37,6 +38,7 @@
 import java.lang.ref.WeakReference;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.UUID;
 
 /**
  * The SliceStoreBroadcastReceiver listens for {@link SliceStore#ACTION_START_SLICE_STORE} from the
@@ -47,6 +49,12 @@
 public class SliceStoreBroadcastReceiver extends BroadcastReceiver{
     private static final String TAG = "SliceStoreBroadcastReceiver";
 
+    /**
+     * UUID to report an anomaly when receiving a PendingIntent from an application or process
+     * other than the Phone process.
+     */
+    private static final String UUID_BAD_PENDING_INTENT = "c360246e-95dc-4abf-9dc1-929a76cd7e53";
+
     /** Weak references to {@link SliceStoreActivity} for each capability, if it exists. */
     private static final Map<Integer, WeakReference<SliceStoreActivity>> sSliceStoreActivities =
             new HashMap<>();
@@ -102,6 +110,28 @@
     }
 
     /**
+     * Send the PendingIntent containing the corresponding SliceStore response with additional data.
+     *
+     * @param context The Context to use to send the PendingIntent.
+     * @param intent The Intent containing the PendingIntent extra.
+     * @param extra The extra to get the PendingIntent to send.
+     * @param data The Intent containing additional data to send with the PendingIntent.
+     */
+    public static void sendSliceStoreResponseWithData(@NonNull Context context,
+            @NonNull Intent intent, @NonNull String extra, @NonNull Intent data) {
+        PendingIntent pendingIntent = intent.getParcelableExtra(extra, PendingIntent.class);
+        if (pendingIntent == null) {
+            loge("PendingIntent does not exist for extra: " + extra);
+            return;
+        }
+        try {
+            pendingIntent.send(context, 0 /* unused */, data);
+        } catch (PendingIntent.CanceledException e) {
+            loge("Unable to send " + getPendingIntentType(extra) + " intent: " + e);
+        }
+    }
+
+    /**
      * Check whether the Intent is valid and can be used to complete purchases in the SliceStore.
      * This checks that all necessary extras exist and that the values are valid.
      *
@@ -139,7 +169,8 @@
         return isPendingIntentValid(intent, SliceStore.EXTRA_INTENT_CANCELED)
                 && isPendingIntentValid(intent, SliceStore.EXTRA_INTENT_CARRIER_ERROR)
                 && isPendingIntentValid(intent, SliceStore.EXTRA_INTENT_REQUEST_FAILED)
-                && isPendingIntentValid(intent, SliceStore.EXTRA_INTENT_NOT_DEFAULT_DATA);
+                && isPendingIntentValid(intent, SliceStore.EXTRA_INTENT_NOT_DEFAULT_DATA)
+                && isPendingIntentValid(intent, SliceStore.EXTRA_INTENT_SUCCESS);
     }
 
     private static boolean isPendingIntentValid(@NonNull Intent intent, @NonNull String extra) {
@@ -148,12 +179,20 @@
         if (pendingIntent == null) {
             loge("isPendingIntentValid: " + intentType + " intent not found.");
             return false;
-        } else if (pendingIntent.getCreatorPackage().equals(TelephonyManager.PHONE_PROCESS_NAME)) {
-            return true;
         }
-        loge("isPendingIntentValid: " + intentType + " intent was created by "
-                + pendingIntent.getCreatorPackage() + " instead of the phone process.");
-        return false;
+        String creatorPackage = pendingIntent.getCreatorPackage();
+        if (!creatorPackage.equals(TelephonyManager.PHONE_PROCESS_NAME)) {
+            String logStr = "isPendingIntentValid: " + intentType + " intent was created by "
+                    + creatorPackage + " instead of the phone process.";
+            loge(logStr);
+            AnomalyReporter.reportAnomaly(UUID.fromString(UUID_BAD_PENDING_INTENT), logStr);
+            return false;
+        }
+        if (!pendingIntent.isBroadcast()) {
+            loge("isPendingIntentValid: " + intentType + " intent is not a broadcast.");
+            return false;
+        }
+        return true;
     }
 
     @NonNull private static String getPendingIntentType(@NonNull String extra) {
@@ -162,6 +201,7 @@
             case SliceStore.EXTRA_INTENT_CARRIER_ERROR: return "carrier error";
             case SliceStore.EXTRA_INTENT_REQUEST_FAILED: return "request failed";
             case SliceStore.EXTRA_INTENT_NOT_DEFAULT_DATA: return "not default data";
+            case SliceStore.EXTRA_INTENT_SUCCESS: return "success";
             default: {
                 loge("Unknown pending intent extra: " + extra);
                 return "unknown(" + extra + ")";
@@ -292,7 +332,6 @@
             logd("Closing SliceStore WebView since the user did not complete the purchase "
                     + "in time.");
             sSliceStoreActivities.get(capability).get().finishAndRemoveTask();
-            // TODO: Display a toast to indicate timeout for better UX?
         }
     }
 
diff --git a/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreWebInterface.java b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreWebInterface.java
new file mode 100644
index 0000000..ab5d080
--- /dev/null
+++ b/packages/CarrierDefaultApp/src/com/android/carrierdefaultapp/SliceStoreWebInterface.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2022 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.carrierdefaultapp;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.telephony.TelephonyManager;
+import android.webkit.JavascriptInterface;
+
+import com.android.phone.slicestore.SliceStore;
+
+/**
+ * SliceStore web interface class allowing carrier websites to send responses back to SliceStore
+ * using JavaScript.
+ */
+public class SliceStoreWebInterface {
+    @NonNull SliceStoreActivity mActivity;
+
+    public SliceStoreWebInterface(@NonNull SliceStoreActivity activity) {
+        mActivity = activity;
+    }
+    /**
+     * Interface method allowing the carrier website to get the premium capability
+     * that was requested to purchase.
+     *
+     * This can be called using the JavaScript below:
+     * <script type="text/javascript">
+     *     function getRequestedCapability(duration) {
+     *         SliceStoreWebInterface.getRequestedCapability();
+     *     }
+     * </script>
+     */
+    @JavascriptInterface
+    @TelephonyManager.PremiumCapability public int getRequestedCapability() {
+        return mActivity.mCapability;
+    }
+
+    /**
+     * Interface method allowing the carrier website to notify the SliceStore of a successful
+     * premium capability purchase and the duration for which the premium capability is purchased.
+     *
+     * This can be called using the JavaScript below:
+     * <script type="text/javascript">
+     *     function notifyPurchaseSuccessful(duration_ms_long = 0) {
+     *         SliceStoreWebInterface.notifyPurchaseSuccessful(duration_ms_long);
+     *     }
+     * </script>
+     *
+     * @param duration The duration for which the premium capability is purchased in milliseconds.
+     */
+    @JavascriptInterface
+    public void notifyPurchaseSuccessful(long duration) {
+        mActivity.onPurchaseSuccessful(duration);
+    }
+
+    /**
+     * Interface method allowing the carrier website to notify the SliceStore of a failed
+     * premium capability purchase.
+     *
+     * This can be called using the JavaScript below:
+     * <script type="text/javascript">
+     *     function notifyPurchaseFailed() {
+     *         SliceStoreWebInterface.notifyPurchaseFailed();
+     *     }
+     * </script>
+     *
+     * @param failureCode The failure code.
+     * @param failureReason If the failure code is {@link SliceStore#FAILURE_CODE_UNKNOWN},
+     *                      the human-readable reason for failure.
+     */
+    @JavascriptInterface
+    public void notifyPurchaseFailed(@SliceStore.FailureCode int failureCode,
+            @Nullable String failureReason) {
+        mActivity.onPurchaseFailed(failureCode, failureReason);
+    }
+}
diff --git a/packages/CredentialManager/res/drawable/ic_profile.xml b/packages/CredentialManager/res/drawable/ic_profile.xml
new file mode 100644
index 0000000..ae65940
--- /dev/null
+++ b/packages/CredentialManager/res/drawable/ic_profile.xml
@@ -0,0 +1,11 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android" xmlns:aapt="http://schemas.android.com/aapt"
+        android:viewportWidth="46"
+        android:viewportHeight="46"
+        android:width="46dp"
+        android:height="46dp">
+    <path
+        android:pathData="M45.4247 22.9953C45.4247 35.0229 35.4133 44.7953 23.0359 44.7953C10.6585 44.7953 0.646973 35.0229 0.646973 22.9953C0.646973 10.9677 10.6585 1.19531 23.0359 1.19531C35.4133 1.19531 45.4247 10.9677 45.4247 22.9953Z"
+        android:strokeColor="#202124"
+        android:strokeAlpha="0.13"
+        android:strokeWidth="1" />
+</vector>
\ No newline at end of file
diff --git a/packages/CredentialManager/res/values/strings.xml b/packages/CredentialManager/res/values/strings.xml
index c6779fa..08ab1b4 100644
--- a/packages/CredentialManager/res/values/strings.xml
+++ b/packages/CredentialManager/res/values/strings.xml
@@ -22,4 +22,9 @@
   <string name="choose_create_option_description">You can use saved <xliff:g id="type">%1$s</xliff:g> on any device. It will be saved to <xliff:g id="providerInfoDisplayName">%2$s</xliff:g> for <xliff:g id="createInfoDisplayName">%3$s</xliff:g></string>
   <string name="more_options_title_multiple_options"><xliff:g id="providerInfoDisplayName">%1$s</xliff:g> for <xliff:g id="createInfoTitle">%2$s</xliff:g></string>
   <string name="more_options_title_one_option"><xliff:g id="providerInfoDisplayName">%1$s</xliff:g></string>
+  <string name="more_options_usage_data"><xliff:g id="passwordsNumber">%1$s</xliff:g> passwords and <xliff:g id="passkeyssNumber">%2$s</xliff:g> passkeys saved</string>
+  <string name="passkeys">passkeys</string>
+  <string name="passwords">passwords</string>
+  <string name="sign_ins">sign-ins</string>
+  <string name="createOptionInfo_icon_description">CreateOptionInfo credentialType icon</string>
 </resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index 56fb1a9..9e9d16f 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -23,6 +23,8 @@
 import android.credentials.CreateCredentialRequest
 import android.credentials.ui.Constants
 import android.credentials.ui.Entry
+import android.credentials.ui.CreateCredentialProviderData
+import android.credentials.ui.GetCredentialProviderData
 import android.credentials.ui.ProviderData
 import android.credentials.ui.RequestInfo
 import android.credentials.ui.BaseDialogResult
@@ -31,12 +33,14 @@
 import android.os.Binder
 import android.os.Bundle
 import android.os.ResultReceiver
+import com.android.credentialmanager.createflow.ActiveEntry
 import com.android.credentialmanager.createflow.CreatePasskeyUiState
 import com.android.credentialmanager.createflow.CreateScreenState
+import com.android.credentialmanager.createflow.ProviderInfo
 import com.android.credentialmanager.createflow.RequestDisplayInfo
 import com.android.credentialmanager.getflow.GetCredentialUiState
 import com.android.credentialmanager.getflow.GetScreenState
-import com.android.credentialmanager.jetpack.CredentialEntryUi.Companion.TYPE_PUBLIC_KEY_CREDENTIAL
+import com.android.credentialmanager.jetpack.provider.CredentialEntryUi.Companion.TYPE_PUBLIC_KEY_CREDENTIAL
 
 // Consider repo per screen, similar to view model?
 class CredentialManagerRepo(
@@ -54,10 +58,22 @@
       RequestInfo::class.java
     ) ?: testRequestInfo()
 
-    providerList = intent.extras?.getParcelableArrayList(
-      ProviderData.EXTRA_PROVIDER_DATA_LIST,
-      ProviderData::class.java
-    ) ?: testProviderList()
+    providerList = when (requestInfo.type) {
+      RequestInfo.TYPE_CREATE ->
+        intent.extras?.getParcelableArrayList(
+                ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST,
+                CreateCredentialProviderData::class.java
+        ) ?: testCreateCredentialProviderList()
+      RequestInfo.TYPE_GET ->
+        intent.extras?.getParcelableArrayList(
+          ProviderData.EXTRA_ENABLED_PROVIDER_DATA_LIST,
+          GetCredentialProviderData::class.java
+        ) ?: testGetCredentialProviderList()
+      else -> {
+        // TODO: fail gracefully
+        throw IllegalStateException("Unrecognized request type: ${requestInfo.type}")
+      }
+    }
 
     resultReceiver = intent.getParcelableExtra(
       Constants.EXTRA_RESULT_RECEIVER,
@@ -84,7 +100,9 @@
   }
 
   fun getCredentialInitialUiState(): GetCredentialUiState {
-    val providerList = GetFlowUtils.toProviderList(providerList, context)
+    val providerList = GetFlowUtils.toProviderList(
+      // TODO: handle runtime cast error
+      providerList as List<GetCredentialProviderData>, context)
     // TODO: covert from real requestInfo
     val requestDisplayInfo = com.android.credentialmanager.getflow.RequestDisplayInfo(
       "Elisa Beckett",
@@ -100,7 +118,13 @@
   }
 
   fun createPasskeyInitialUiState(): CreatePasskeyUiState {
-    val providerList = CreateFlowUtils.toProviderList(providerList, context)
+    val providerList = CreateFlowUtils.toProviderList(
+      // Handle runtime cast error
+      providerList as List<CreateCredentialProviderData>, context)
+    var hasDefault = false
+    var defaultProvider: ProviderInfo = providerList.first()
+    providerList.forEach{providerInfo ->
+      if (providerInfo.isDefault) {hasDefault = true; defaultProvider = providerInfo} }
     // TODO: covert from real requestInfo
     val requestDisplayInfo = RequestDisplayInfo(
       "Elisa Beckett",
@@ -109,8 +133,12 @@
       "tribank")
     return CreatePasskeyUiState(
       providers = providerList,
-      currentScreenState = CreateScreenState.PASSKEY_INTRO,
+      if (hasDefault)
+      {CreateScreenState.CREATION_OPTION_SELECTION} else {CreateScreenState.PASSKEY_INTRO},
       requestDisplayInfo,
+      if (hasDefault) {
+        ActiveEntry(defaultProvider, defaultProvider.createOptions.first())
+      } else null
     )
   }
 
@@ -130,43 +158,64 @@
   }
 
   // TODO: below are prototype functionalities. To be removed for productionization.
-  private fun testProviderList(): List<ProviderData> {
+  private fun testCreateCredentialProviderList(): List<CreateCredentialProviderData> {
     return listOf(
-      ProviderData.Builder(
-        "com.google",
-        "Google Password Manager",
-        Icon.createWithResource(context, R.drawable.ic_launcher_foreground))
+      CreateCredentialProviderData
+        .Builder("com.google/com.google.CredentialManagerService")
+        .setSaveEntries(
+          listOf<Entry>(
+            newEntry("key1", "subkey-1", "elisa.beckett@gmail.com",
+              20, 7, 27, 10000),
+            newEntry("key1", "subkey-2", "elisa.work@google.com",
+              20, 7, 27, 11000),
+          )
+        )
+        .setIsDefaultProvider(true)
+        .build(),
+      CreateCredentialProviderData
+        .Builder("com.dashlane/com.dashlane.CredentialManagerService")
+        .setSaveEntries(
+          listOf<Entry>(
+            newEntry("key1", "subkey-3", "elisa.beckett@dashlane.com",
+              20, 7, 27, 30000),
+            newEntry("key1", "subkey-4", "elisa.work@dashlane.com",
+              20, 7, 27, 31000),
+          )
+        )
+        .build(),
+    )
+  }
+
+  private fun testGetCredentialProviderList(): List<GetCredentialProviderData> {
+    return listOf(
+      GetCredentialProviderData.Builder("com.google/com.google.CredentialManagerService")
         .setCredentialEntries(
           listOf<Entry>(
             newEntry("key1", "subkey-1", "elisa.beckett@gmail.com",
-              "Elisa Backett", "20 passwords and 7 passkeys saved"),
+              20, 7, 27, 10000),
             newEntry("key1", "subkey-2", "elisa.work@google.com",
-              "Elisa Backett Work", "20 passwords and 7 passkeys saved"),
+              20, 7, 27, 11000),
           )
         ).setActionChips(
           listOf<Entry>(
-            newEntry("key2", "subkey-1", "Go to Settings", "",
-                     "20 passwords and 7 passkeys saved"),
-            newEntry("key2", "subkey-2", "Switch Account", "",
-                     "20 passwords and 7 passkeys saved"),
+            newEntry("key2", "subkey-1", "Go to Settings",
+              20, 7, 27, 20000),
+            newEntry("key2", "subkey-2", "Switch Account",
+              20, 7, 27, 21000),
           ),
         ).build(),
-      ProviderData.Builder(
-        "com.dashlane",
-        "Dashlane",
-        Icon.createWithResource(context, R.drawable.ic_launcher_foreground))
+      GetCredentialProviderData.Builder("com.dashlane/com.dashlane.CredentialManagerService")
         .setCredentialEntries(
           listOf<Entry>(
             newEntry("key1", "subkey-3", "elisa.beckett@dashlane.com",
-              "Elisa Backett", "20 passwords and 7 passkeys saved"),
+              20, 7, 27, 30000),
             newEntry("key1", "subkey-4", "elisa.work@dashlane.com",
-              "Elisa Backett Work", "20 passwords and 7 passkeys saved"),
+              20, 7, 27, 31000),
           )
         ).setActionChips(
           listOf<Entry>(
             newEntry("key2", "subkey-3", "Manage Accounts",
-              "Manage your accounts in the dashlane app",
-                     "20 passwords and 7 passkeys saved"),
+              20, 7, 27, 40000),
           ),
         ).build(),
     )
@@ -175,20 +224,32 @@
   private fun newEntry(
     key: String,
     subkey: String,
-    title: String,
-    subtitle: String,
-    usageData: String
+    providerDisplayName: String,
+    passwordCount: Int,
+    passkeyCount: Int,
+    totalCredentialCount: Int,
+    lastUsedTimeMillis: Long,
   ): Entry {
     val slice = Slice.Builder(
       Entry.CREDENTIAL_MANAGER_ENTRY_URI, SliceSpec(Entry.VERSION, 1)
     )
-      .addText(title, null, listOf(Entry.HINT_TITLE))
-      .addText(subtitle, null, listOf(Entry.HINT_SUBTITLE))
+      .addText(
+        providerDisplayName, null, listOf(Entry.HINT_USER_PROVIDER_ACCOUNT_NAME))
       .addIcon(
         Icon.createWithResource(context, R.drawable.ic_passkey),
         null,
-        listOf(Entry.HINT_ICON))
-      .addText(usageData, Slice.SUBTYPE_MESSAGE, listOf(Entry.HINT_SUBTITLE))
+        listOf(Entry.HINT_CREDENTIAL_TYPE_ICON))
+      .addIcon(
+        Icon.createWithResource(context, R.drawable.ic_profile),
+        null,
+        listOf(Entry.HINT_PROFILE_ICON))
+      .addInt(
+        passwordCount, null, listOf(Entry.HINT_PASSWORD_COUNT))
+      .addInt(
+        passkeyCount, null, listOf(Entry.HINT_PASSKEY_COUNT))
+      .addInt(
+        totalCredentialCount, null, listOf(Entry.HINT_TOTAL_CREDENTIAL_COUNT))
+      .addLong(lastUsedTimeMillis, null, listOf(Entry.HINT_LAST_USED_TIME_MILLIS))
       .build()
     return Entry(
       key,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
index e037db7..3a8e975 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/DataConverter.kt
@@ -18,27 +18,30 @@
 
 import android.content.Context
 import android.credentials.ui.Entry
-import android.credentials.ui.ProviderData
+import android.credentials.ui.GetCredentialProviderData
+import android.credentials.ui.CreateCredentialProviderData
 import com.android.credentialmanager.createflow.CreateOptionInfo
 import com.android.credentialmanager.getflow.CredentialOptionInfo
 import com.android.credentialmanager.getflow.ProviderInfo
+import com.android.credentialmanager.jetpack.provider.SaveEntryUi
 
 /** Utility functions for converting CredentialManager data structures to or from UI formats. */
 class GetFlowUtils {
   companion object {
 
     fun toProviderList(
-      providerDataList: List<ProviderData>,
+      providerDataList: List<GetCredentialProviderData>,
       context: Context,
     ): List<ProviderInfo> {
       return providerDataList.map {
         ProviderInfo(
           // TODO: replace to extract from the service data structure when available
           icon = context.getDrawable(R.drawable.ic_passkey)!!,
-          name = it.providerId,
-          displayName = it.providerDisplayName,
+          name = it.providerFlattenedComponentName,
+          // TODO: get the service display name and icon from the component name.
+          displayName = it.providerFlattenedComponentName,
           credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
-          credentialOptions = toCredentialOptionInfoList(it.credentialEntries, context)
+          credentialOptions = toCredentialOptionInfoList(it.credentialEntries, context),
         )
       }
     }
@@ -57,8 +60,6 @@
           // TODO: remove fallbacks
           icon = credentialEntryUi.icon?.loadDrawable(context)
             ?: context.getDrawable(R.drawable.ic_passkey)!!,
-          title = credentialEntryUi.userName.toString(),
-          subtitle = credentialEntryUi.displayName?.toString() ?: "Unknown display name",
           entryKey = it.key,
           entrySubkey = it.subkey,
           usageData = credentialEntryUi.usageData?.toString() ?: "Unknown usageData",
@@ -72,17 +73,17 @@
   companion object {
 
     fun toProviderList(
-      providerDataList: List<ProviderData>,
+      providerDataList: List<CreateCredentialProviderData>,
       context: Context,
     ): List<com.android.credentialmanager.createflow.ProviderInfo> {
       return providerDataList.map {
         com.android.credentialmanager.createflow.ProviderInfo(
           // TODO: replace to extract from the service data structure when available
           icon = context.getDrawable(R.drawable.ic_passkey)!!,
-          name = it.providerId,
-          displayName = it.providerDisplayName,
-          credentialTypeIcon = context.getDrawable(R.drawable.ic_passkey)!!,
-          createOptions = toCreationOptionInfoList(it.credentialEntries, context),
+          name = it.providerFlattenedComponentName,
+          displayName = it.providerFlattenedComponentName,
+          createOptions = toCreationOptionInfoList(it.saveEntries, context),
+          isDefault = it.isDefaultProvider,
         )
       }
     }
@@ -96,13 +97,17 @@
 
         return@map CreateOptionInfo(
           // TODO: remove fallbacks
-          icon = saveEntryUi.icon?.loadDrawable(context)
-            ?: context.getDrawable(R.drawable.ic_passkey)!!,
-          title = saveEntryUi.title.toString(),
-          subtitle = saveEntryUi.subTitle?.toString() ?: "Unknown subtitle",
           entryKey = it.key,
           entrySubkey = it.subkey,
-          usageData = saveEntryUi.usageData?.toString() ?: "Unknown usageData",
+          userProviderDisplayName = saveEntryUi.userProviderAccountName as String,
+          credentialTypeIcon = saveEntryUi.credentialTypeIcon?.loadDrawable(context)
+            ?: context.getDrawable(R.drawable.ic_passkey)!!,
+          profileIcon = saveEntryUi.profileIcon?.loadDrawable(context)
+            ?: context.getDrawable(R.drawable.ic_profile)!!,
+          passwordCount = saveEntryUi.passwordCount ?: 0,
+          passkeyCount = saveEntryUi.passkeyCount ?: 0,
+          totalCredentialCount = saveEntryUi.totalCredentialCount ?: 0,
+          lastUsedTimeMillis = saveEntryUi.lastUsedTimeMillis ?: 0,
         )
       }
     }
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/SaveEntryUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/SaveEntryUi.kt
deleted file mode 100644
index cd52197..0000000
--- a/packages/CredentialManager/src/com/android/credentialmanager/SaveEntryUi.kt
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Copyright (C) 2022 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.credentialmanager
-
-import android.app.slice.Slice
-import android.credentials.ui.Entry
-import android.graphics.drawable.Icon
-
-/**
- * UI representation for a save entry used during the create credential flow.
- *
- * TODO: move to jetpack.
- */
-class SaveEntryUi(
-  val title: CharSequence,
-  val subTitle: CharSequence?,
-  val icon: Icon?,
-  val usageData: CharSequence?,
-  // TODO: add
-) {
-  companion object {
-    fun fromSlice(slice: Slice): SaveEntryUi {
-      val items = slice.items
-
-      var title: String? = null
-      var subTitle: String? = null
-      var icon: Icon? = null
-      var usageData: String? = null
-
-      items.forEach {
-        if (it.hasHint(Entry.HINT_ICON)) {
-          icon = it.icon
-        } else if (it.hasHint(Entry.HINT_SUBTITLE) && it.subType == null) {
-          subTitle = it.text.toString()
-        } else if (it.hasHint(Entry.HINT_TITLE)) {
-          title = it.text.toString()
-        } else if (it.hasHint(Entry.HINT_SUBTITLE) && it.subType == Slice.SUBTYPE_MESSAGE) {
-          usageData = it.text.toString()
-        }
-      }
-      // TODO: fail NPE more elegantly.
-      return SaveEntryUi(title!!, subTitle, icon, usageData)
-    }
-  }
-}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
index cb2bf10..d7e5ee8 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreateModel.kt
@@ -22,19 +22,27 @@
   val icon: Drawable,
   val name: String,
   val displayName: String,
-  val credentialTypeIcon: Drawable,
   val createOptions: List<CreateOptionInfo>,
+  val isDefault: Boolean,
 )
 
-data class CreateOptionInfo(
-  val icon: Drawable,
-  val title: String,
-  val subtitle: String,
+open class EntryInfo (
   val entryKey: String,
   val entrySubkey: String,
-  val usageData: String
 )
 
+class CreateOptionInfo(
+  entryKey: String,
+  entrySubkey: String,
+  val userProviderDisplayName: String,
+  val credentialTypeIcon: Drawable,
+  val profileIcon: Drawable,
+  val passwordCount: Int,
+  val passkeyCount: Int,
+  val totalCredentialCount: Int,
+  val lastUsedTimeMillis: Long?,
+) : EntryInfo(entryKey, entrySubkey)
+
 data class RequestDisplayInfo(
   val userName: String,
   val displayName: String,
@@ -48,7 +56,7 @@
  */
 data class ActiveEntry (
   val activeProvider: ProviderInfo,
-  val activeCreateOptionInfo: CreateOptionInfo,
+  val activeEntryInfo: EntryInfo,
 )
 
 /** The name of the current screen. */
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
index 8e30208..13a892f 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyComponents.kt
@@ -39,8 +39,8 @@
 import androidx.compose.ui.unit.dp
 import androidx.core.graphics.drawable.toBitmap
 import com.android.credentialmanager.R
-import com.android.credentialmanager.jetpack.CredentialEntryUi.Companion.TYPE_PASSWORD_CREDENTIAL
-import com.android.credentialmanager.jetpack.CredentialEntryUi.Companion.TYPE_PUBLIC_KEY_CREDENTIAL
+import com.android.credentialmanager.jetpack.provider.CredentialEntryUi.Companion.TYPE_PASSWORD_CREDENTIAL
+import com.android.credentialmanager.jetpack.provider.CredentialEntryUi.Companion.TYPE_PUBLIC_KEY_CREDENTIAL
 import com.android.credentialmanager.ui.theme.Grey100
 import com.android.credentialmanager.ui.theme.Shapes
 import com.android.credentialmanager.ui.theme.Typography
@@ -74,7 +74,7 @@
         CreateScreenState.CREATION_OPTION_SELECTION -> CreationSelectionCard(
           requestDisplayInfo = uiState.requestDisplayInfo,
           providerInfo = uiState.activeEntry?.activeProvider!!,
-          createOptionInfo = uiState.activeEntry.activeCreateOptionInfo,
+          createOptionInfo = uiState.activeEntry.activeEntryInfo as CreateOptionInfo,
           onOptionSelected = viewModel::onPrimaryCreateOptionInfoSelected,
           onConfirm = viewModel::onPrimaryCreateOptionInfoSelected,
           onCancel = viewModel::onCancel,
@@ -408,7 +408,7 @@
   ) {
     Column() {
       Icon(
-        bitmap = providerInfo.credentialTypeIcon.toBitmap().asImageBitmap(),
+        bitmap = createOptionInfo.credentialTypeIcon.toBitmap().asImageBitmap(),
         contentDescription = null,
         tint = Color.Unspecified,
         modifier = Modifier.align(alignment = Alignment.CenterHorizontally).padding(all = 24.dp)
@@ -436,12 +436,12 @@
         text = stringResource(
           R.string.choose_create_option_description,
           when (requestDisplayInfo.type) {
-            TYPE_PUBLIC_KEY_CREDENTIAL -> "passkeys"
-            TYPE_PASSWORD_CREDENTIAL -> "passwords"
-            else -> "sign-ins"
+            TYPE_PUBLIC_KEY_CREDENTIAL -> stringResource(R.string.passkeys)
+            TYPE_PASSWORD_CREDENTIAL -> stringResource(R.string.passwords)
+            else -> stringResource(R.string.sign_ins)
           },
           providerInfo.displayName,
-          createOptionInfo.title),
+          createOptionInfo.userProviderDisplayName),
         style = Typography.body1,
         modifier = Modifier.padding(all = 24.dp).align(alignment = Alignment.CenterHorizontally)
       )
@@ -455,8 +455,11 @@
           verticalArrangement = Arrangement.spacedBy(2.dp)
         ) {
             item {
-              PrimaryCreateOptionRow(requestDisplayInfo = requestDisplayInfo,
-                onOptionSelected = onOptionSelected)
+              PrimaryCreateOptionRow(
+                requestDisplayInfo = requestDisplayInfo,
+                createOptionInfo = createOptionInfo,
+                onOptionSelected = onOptionSelected
+              )
             }
         }
       }
@@ -506,12 +509,17 @@
 @Composable
 fun PrimaryCreateOptionRow(
   requestDisplayInfo: RequestDisplayInfo,
+  createOptionInfo: CreateOptionInfo,
   onOptionSelected: () -> Unit
 ) {
   Chip(
     modifier = Modifier.fillMaxWidth(),
     onClick = onOptionSelected,
-    // TODO: Add an icon generated by provider according to requestDisplayInfo type
+    leadingIcon = {
+      Image(modifier = Modifier.size(24.dp, 24.dp).padding(start = 10.dp),
+        bitmap = createOptionInfo.credentialTypeIcon.toBitmap().asImageBitmap(),
+        contentDescription = stringResource(R.string.createOptionInfo_icon_description))
+    },
     colors = ChipDefaults.chipColors(
       backgroundColor = Grey100,
       leadingIconContentColor = Grey100
@@ -545,7 +553,7 @@
         onClick = onOptionSelected,
         leadingIcon = {
             Image(modifier = Modifier.size(24.dp, 24.dp).padding(start = 10.dp),
-                bitmap = createOptionInfo.icon.toBitmap().asImageBitmap(),
+                bitmap = createOptionInfo.profileIcon.toBitmap().asImageBitmap(),
                 // painter = painterResource(R.drawable.ic_passkey),
                 // TODO: add description.
                 contentDescription = "")
@@ -561,14 +569,15 @@
                 text =
                 if (providerInfo.createOptions.size > 1)
                 {stringResource(R.string.more_options_title_multiple_options,
-                  providerInfo.displayName, createOptionInfo.title)} else {
+                  providerInfo.displayName, createOptionInfo.userProviderDisplayName)} else {
                   stringResource(R.string.more_options_title_one_option,
                     providerInfo.displayName)},
                 style = Typography.h6,
                 modifier = Modifier.padding(top = 16.dp)
             )
             Text(
-                text = createOptionInfo.usageData,
+                text = stringResource(R.string.more_options_usage_data,
+                  createOptionInfo.passwordCount, createOptionInfo.passkeyCount),
                 style = Typography.body2,
                 modifier = Modifier.padding(bottom = 16.dp)
             )
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt
index 615da4e..2e9758a 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/createflow/CreatePasskeyViewModel.kt
@@ -73,21 +73,6 @@
     )
   }
 
-  fun onCreateOptionSelected(entryKey: String, entrySubkey: String) {
-    Log.d(
-      "Account Selector",
-      "Option selected for creation: {key = $entryKey, subkey = $entrySubkey}"
-    )
-    CredentialManagerRepo.getInstance().onOptionSelected(
-      uiState.activeEntry?.activeProvider!!.name,
-      entryKey,
-      entrySubkey
-    )
-    dialogResult.value = DialogResult(
-      ResultState.COMPLETE,
-    )
-  }
-
   fun getProviderInfoByName(providerName: String): ProviderInfo {
     return uiState.providers.single {
       it.name.equals(providerName)
@@ -126,18 +111,18 @@
   }
 
   fun onPrimaryCreateOptionInfoSelected() {
-    var createOptionEntryKey = uiState.activeEntry?.activeCreateOptionInfo?.entryKey
-    var createOptionEntrySubkey = uiState.activeEntry?.activeCreateOptionInfo?.entrySubkey
+    val entryKey = uiState.activeEntry?.activeEntryInfo?.entryKey
+    val entrySubkey = uiState.activeEntry?.activeEntryInfo?.entrySubkey
     Log.d(
       "Account Selector",
       "Option selected for creation: " +
-              "{key = $createOptionEntryKey, subkey = $createOptionEntrySubkey}"
+              "{key = $entryKey, subkey = $entrySubkey}"
     )
-    if (createOptionEntryKey != null && createOptionEntrySubkey != null) {
+    if (entryKey != null && entrySubkey != null) {
       CredentialManagerRepo.getInstance().onOptionSelected(
         uiState.activeEntry?.activeProvider!!.name,
-        createOptionEntryKey,
-        createOptionEntrySubkey
+        entryKey,
+        entrySubkey
       )
     } else {
       TODO("Gracefully handle illegal state.")
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
index e3398c0..8b81083 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetCredentialComponents.kt
@@ -184,12 +184,12 @@
   ) {
     Column() {
       Text(
-        text = credentialOptionInfo.title,
+        text = credentialOptionInfo.entryKey,
         style = Typography.h6,
         modifier = Modifier.padding(top = 16.dp)
       )
       Text(
-        text = credentialOptionInfo.subtitle,
+        text = credentialOptionInfo.entrySubkey,
         style = Typography.body2,
         modifier = Modifier.padding(bottom = 16.dp)
       )
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
index b6ecd37..b427de6 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
@@ -24,17 +24,21 @@
   val displayName: String,
   val credentialTypeIcon: Drawable,
   val credentialOptions: List<CredentialOptionInfo>,
+  // TODO: Add the authenticationOption
 )
 
-data class CredentialOptionInfo(
-  val icon: Drawable,
-  val title: String,
-  val subtitle: String,
+open class EntryInfo (
   val entryKey: String,
   val entrySubkey: String,
-  val usageData: String
 )
 
+class CredentialOptionInfo(
+  entryKey: String,
+  entrySubkey: String,
+  val icon: Drawable,
+  val usageData: String,
+) : EntryInfo(entryKey, entrySubkey)
+
 data class RequestDisplayInfo(
   val userName: String,
   val displayName: String,
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreateCredentialRequest.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreateCredentialRequest.kt
new file mode 100644
index 0000000..7e7dbde
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreateCredentialRequest.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2022 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.credentialmanager.jetpack.developer
+
+import android.credentials.Credential
+import android.os.Bundle
+
+/**
+ * Base request class for registering a credential.
+ *
+ * @property type the credential type
+ * @property data the request data in the [Bundle] format
+ * @property requireSystemProvider true if must only be fulfilled by a system provider and false
+ *                              otherwise
+ */
+open class CreateCredentialRequest(
+        val type: String,
+        val data: Bundle,
+        val requireSystemProvider: Boolean,
+) {
+    companion object {
+        @JvmStatic
+        fun createFrom(from: android.credentials.CreateCredentialRequest): CreateCredentialRequest {
+            return try {
+                when (from.type) {
+                    Credential.TYPE_PASSWORD_CREDENTIAL ->
+                        CreatePasswordRequest.createFrom(from.data)
+                    PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL ->
+                        CreatePublicKeyCredentialBaseRequest.createFrom(from.data)
+                    else ->
+                        CreateCredentialRequest(from.type, from.data, from.requireSystemProvider())
+                }
+            } catch (e: FrameworkClassParsingException) {
+                CreateCredentialRequest(from.type, from.data, from.requireSystemProvider())
+            }
+        }
+    }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePasswordRequest.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePasswordRequest.kt
new file mode 100644
index 0000000..f0da9f9
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePasswordRequest.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 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.credentialmanager.jetpack.developer
+
+import android.credentials.Credential
+import android.os.Bundle
+
+/**
+ * A request to save the user password credential with their password provider.
+ *
+ * @property id the user id associated with the password
+ * @property password the password
+ * @throws NullPointerException If [id] is null
+ * @throws NullPointerException If [password] is null
+ * @throws IllegalArgumentException If [password] is empty
+ */
+class CreatePasswordRequest constructor(
+        val id: String,
+        val password: String,
+) : CreateCredentialRequest(
+        Credential.TYPE_PASSWORD_CREDENTIAL,
+        toBundle(id, password),
+        false,
+) {
+
+    init {
+        require(password.isNotEmpty()) { "password should not be empty" }
+    }
+
+    companion object {
+        const val BUNDLE_KEY_ID = "androidx.credentials.BUNDLE_KEY_ID"
+        const val BUNDLE_KEY_PASSWORD = "androidx.credentials.BUNDLE_KEY_PASSWORD"
+
+        @JvmStatic
+        internal fun toBundle(id: String, password: String): Bundle {
+            val bundle = Bundle()
+            bundle.putString(BUNDLE_KEY_ID, id)
+            bundle.putString(BUNDLE_KEY_PASSWORD, password)
+            return bundle
+        }
+
+        @JvmStatic
+        fun createFrom(data: Bundle): CreatePasswordRequest {
+            try {
+                val id = data.getString(BUNDLE_KEY_ID)
+                val password = data.getString(BUNDLE_KEY_PASSWORD)
+                return CreatePasswordRequest(id!!, password!!)
+            } catch (e: Exception) {
+                throw FrameworkClassParsingException()
+            }
+        }
+    }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialBaseRequest.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialBaseRequest.kt
new file mode 100644
index 0000000..26d61f9
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialBaseRequest.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2022 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.credentialmanager.jetpack.developer
+
+import android.os.Bundle
+
+/**
+ * Base request class for registering a public key credential.
+ *
+ * @property requestJson The request in JSON format
+ * @throws NullPointerException If [requestJson] is null. This is handled by the Kotlin runtime
+ * @throws IllegalArgumentException If [requestJson] is empty
+ *
+ * @hide
+ */
+abstract class CreatePublicKeyCredentialBaseRequest constructor(
+        val requestJson: String,
+        type: String,
+        data: Bundle,
+        requireSystemProvider: Boolean,
+) : CreateCredentialRequest(type, data, requireSystemProvider) {
+
+    init {
+        require(requestJson.isNotEmpty()) { "request json must not be empty" }
+    }
+
+    companion object {
+        const val BUNDLE_KEY_REQUEST_JSON = "androidx.credentials.BUNDLE_KEY_REQUEST_JSON"
+        const val BUNDLE_KEY_SUBTYPE = "androidx.credentials.BUNDLE_KEY_SUBTYPE"
+
+        @JvmStatic
+        fun createFrom(data: Bundle): CreatePublicKeyCredentialBaseRequest {
+            return when (data.getString(BUNDLE_KEY_SUBTYPE)) {
+                CreatePublicKeyCredentialRequest
+                        .BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST ->
+                    CreatePublicKeyCredentialRequestPrivileged.createFrom(data)
+                CreatePublicKeyCredentialRequestPrivileged
+                        .BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST_PRIVILEGED ->
+                    CreatePublicKeyCredentialRequestPrivileged.createFrom(data)
+                else -> throw FrameworkClassParsingException()
+            }
+        }
+    }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialRequest.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialRequest.kt
new file mode 100644
index 0000000..2eda90b
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialRequest.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2022 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.credentialmanager.jetpack.developer
+
+import android.os.Bundle
+
+/**
+ * A request to register a passkey from the user's public key credential provider.
+ *
+ * @property requestJson the request in JSON format
+ * @property allowHybrid defines whether hybrid credentials are allowed to fulfill this request,
+ * true by default
+ * @throws NullPointerException If [requestJson] or [allowHybrid] is null. This is handled by the
+ * Kotlin runtime
+ * @throws IllegalArgumentException If [requestJson] is empty
+ *
+ * @hide
+ */
+class CreatePublicKeyCredentialRequest @JvmOverloads constructor(
+        requestJson: String,
+        @get:JvmName("allowHybrid")
+        val allowHybrid: Boolean = true
+) : CreatePublicKeyCredentialBaseRequest(
+        requestJson,
+        PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL,
+        toBundle(requestJson, allowHybrid),
+        false,
+) {
+    companion object {
+        const val BUNDLE_KEY_ALLOW_HYBRID = "androidx.credentials.BUNDLE_KEY_ALLOW_HYBRID"
+        const val BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST =
+                "androidx.credentials.BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST"
+
+        @JvmStatic
+        internal fun toBundle(requestJson: String, allowHybrid: Boolean): Bundle {
+            val bundle = Bundle()
+            bundle.putString(BUNDLE_KEY_SUBTYPE,
+                    BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST)
+            bundle.putString(BUNDLE_KEY_REQUEST_JSON, requestJson)
+            bundle.putBoolean(BUNDLE_KEY_ALLOW_HYBRID, allowHybrid)
+            return bundle
+        }
+
+        @JvmStatic
+        fun createFrom(data: Bundle): CreatePublicKeyCredentialRequest {
+            try {
+                val requestJson = data.getString(BUNDLE_KEY_REQUEST_JSON)
+                val allowHybrid = data.get(BUNDLE_KEY_ALLOW_HYBRID)
+                return CreatePublicKeyCredentialRequest(requestJson!!, (allowHybrid!!) as Boolean)
+            } catch (e: Exception) {
+                throw FrameworkClassParsingException()
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialRequestPrivileged.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialRequestPrivileged.kt
new file mode 100644
index 0000000..36324f8
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/CreatePublicKeyCredentialRequestPrivileged.kt
@@ -0,0 +1,143 @@
+/*
+ * Copyright (C) 2022 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.credentialmanager.jetpack.developer
+
+import android.os.Bundle
+
+/**
+ * A privileged request to register a passkey from the user’s public key credential provider, where
+ * the caller can modify the rp. Only callers with privileged permission, e.g. user’s default
+ * brower, caBLE, can use this.
+ *
+ * @property requestJson the privileged request in JSON format
+ * @property allowHybrid defines whether hybrid credentials are allowed to fulfill this request,
+ * true by default
+ * @property rp the expected true RP ID which will override the one in the [requestJson]
+ * @property clientDataHash a hash that is used to verify the [rp] Identity
+ * @throws NullPointerException If any of [allowHybrid], [requestJson], [rp], or [clientDataHash] is
+ * null. This is handled by the Kotlin runtime
+ * @throws IllegalArgumentException If any of [requestJson], [rp], or [clientDataHash] is empty
+ *
+ * @hide
+ */
+class CreatePublicKeyCredentialRequestPrivileged @JvmOverloads constructor(
+        requestJson: String,
+        val rp: String,
+        val clientDataHash: String,
+        @get:JvmName("allowHybrid")
+        val allowHybrid: Boolean = true
+) : CreatePublicKeyCredentialBaseRequest(
+        requestJson,
+        PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL,
+        toBundle(requestJson, rp, clientDataHash, allowHybrid),
+        false,
+) {
+
+    init {
+        require(rp.isNotEmpty()) { "rp must not be empty" }
+        require(clientDataHash.isNotEmpty()) { "clientDataHash must not be empty" }
+    }
+
+    /** A builder for [CreatePublicKeyCredentialRequestPrivileged]. */
+    class Builder(var requestJson: String, var rp: String, var clientDataHash: String) {
+
+        private var allowHybrid: Boolean = true
+
+        /**
+         * Sets the privileged request in JSON format.
+         */
+        fun setRequestJson(requestJson: String): Builder {
+            this.requestJson = requestJson
+            return this
+        }
+
+        /**
+         * Sets whether hybrid credentials are allowed to fulfill this request, true by default.
+         */
+        fun setAllowHybrid(allowHybrid: Boolean): Builder {
+            this.allowHybrid = allowHybrid
+            return this
+        }
+
+        /**
+         * Sets the expected true RP ID which will override the one in the [requestJson].
+         */
+        fun setRp(rp: String): Builder {
+            this.rp = rp
+            return this
+        }
+
+        /**
+         * Sets a hash that is used to verify the [rp] Identity.
+         */
+        fun setClientDataHash(clientDataHash: String): Builder {
+            this.clientDataHash = clientDataHash
+            return this
+        }
+
+        /** Builds a [CreatePublicKeyCredentialRequestPrivileged]. */
+        fun build(): CreatePublicKeyCredentialRequestPrivileged {
+            return CreatePublicKeyCredentialRequestPrivileged(this.requestJson,
+                    this.rp, this.clientDataHash, this.allowHybrid)
+        }
+    }
+
+    companion object {
+        const val BUNDLE_KEY_RP = "androidx.credentials.BUNDLE_KEY_RP"
+        const val BUNDLE_KEY_CLIENT_DATA_HASH =
+                "androidx.credentials.BUNDLE_KEY_CLIENT_DATA_HASH"
+        const val BUNDLE_KEY_ALLOW_HYBRID = "androidx.credentials.BUNDLE_KEY_ALLOW_HYBRID"
+        const val BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST_PRIVILEGED =
+                "androidx.credentials.BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST_" +
+                        "PRIVILEGED"
+
+        @JvmStatic
+        internal fun toBundle(
+                requestJson: String,
+                rp: String,
+                clientDataHash: String,
+                allowHybrid: Boolean
+        ): Bundle {
+            val bundle = Bundle()
+            bundle.putString(BUNDLE_KEY_SUBTYPE,
+                    BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST_PRIVILEGED)
+            bundle.putString(BUNDLE_KEY_REQUEST_JSON, requestJson)
+            bundle.putString(BUNDLE_KEY_RP, rp)
+            bundle.putString(BUNDLE_KEY_CLIENT_DATA_HASH, clientDataHash)
+            bundle.putBoolean(BUNDLE_KEY_ALLOW_HYBRID, allowHybrid)
+            return bundle
+        }
+
+        @JvmStatic
+        fun createFrom(data: Bundle): CreatePublicKeyCredentialRequestPrivileged {
+            try {
+                val requestJson = data.getString(BUNDLE_KEY_REQUEST_JSON)
+                val rp = data.getString(BUNDLE_KEY_RP)
+                val clientDataHash = data.getString(BUNDLE_KEY_CLIENT_DATA_HASH)
+                val allowHybrid = data.get(BUNDLE_KEY_ALLOW_HYBRID)
+                return CreatePublicKeyCredentialRequestPrivileged(
+                        requestJson!!,
+                        rp!!,
+                        clientDataHash!!,
+                        (allowHybrid!!) as Boolean,
+                )
+            } catch (e: Exception) {
+                throw FrameworkClassParsingException()
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotifPanelEventsModule.java b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/Credential.kt
similarity index 60%
copy from packages/SystemUI/src/com/android/systemui/shade/NotifPanelEventsModule.java
copy to packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/Credential.kt
index 6772384..ee08e9e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotifPanelEventsModule.java
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/Credential.kt
@@ -14,17 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.systemui.shade;
+package com.android.credentialmanager.jetpack.developer
 
-import com.android.systemui.dagger.SysUISingleton;
+import android.os.Bundle
 
-import dagger.Binds;
-import dagger.Module;
-
-/** Provides a {@link NotifPanelEvents} in {@link SysUISingleton} scope. */
-@Module
-public abstract class NotifPanelEventsModule {
-    @Binds
-    abstract NotifPanelEvents bindPanelEvents(
-            NotificationPanelViewController.PanelEventsEmitter impl);
-}
+/**
+ * Base class for a credential with which the user consented to authenticate to the app.
+ *
+ * @property type the credential type
+ * @property data the credential data in the [Bundle] format.
+ */
+open class Credential(val type: String, val data: Bundle)
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/FrameworkClassParsingException.kt
similarity index 64%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
copy to packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/FrameworkClassParsingException.kt
index 1550ab3..497c272 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/FrameworkClassParsingException.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,6 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.systemui.shared.system;
+package com.android.credentialmanager.jetpack.developer
 
-parcelable RemoteTransitionCompat;
+/**
+ * Internal exception used to indicate a parsing error while converting from a framework type to
+ * a jetpack type.
+ *
+ * @hide
+ */
+internal class FrameworkClassParsingException : Exception()
\ No newline at end of file
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialOption.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialOption.kt
new file mode 100644
index 0000000..eb65241
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialOption.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2022 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.credentialmanager.jetpack.developer
+
+import android.credentials.Credential
+import android.os.Bundle
+
+/**
+ * Base request class for getting a registered credential.
+ *
+ * @property type the credential type
+ * @property data the request data in the [Bundle] format
+ * @property requireSystemProvider true if must only be fulfilled by a system provider and false
+ *                              otherwise
+ */
+open class GetCredentialOption(
+        val type: String,
+        val data: Bundle,
+        val requireSystemProvider: Boolean,
+) {
+    companion object {
+        @JvmStatic
+        fun createFrom(from: android.credentials.GetCredentialOption): GetCredentialOption {
+            return try {
+                when (from.type) {
+                    Credential.TYPE_PASSWORD_CREDENTIAL ->
+                        GetPasswordOption.createFrom(from.data)
+                    PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL ->
+                        GetPublicKeyCredentialBaseOption.createFrom(from.data)
+                    else ->
+                        GetCredentialOption(from.type, from.data, from.requireSystemProvider())
+                }
+            } catch (e: FrameworkClassParsingException) {
+                GetCredentialOption(from.type, from.data, from.requireSystemProvider())
+            }
+        }
+    }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialRequest.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialRequest.kt
new file mode 100644
index 0000000..7f9256e
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetCredentialRequest.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2022 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.credentialmanager.jetpack.developer
+
+/**
+ * Encapsulates a request to get a user credential.
+ *
+ * @property getCredentialOptions the list of [GetCredentialOption] from which the user can choose
+ * one to authenticate to the app
+ * @throws IllegalArgumentException If [getCredentialOptions] is empty
+ */
+class GetCredentialRequest constructor(
+        val getCredentialOptions: List<GetCredentialOption>,
+) {
+
+    init {
+        require(getCredentialOptions.isNotEmpty()) { "credentialRequests should not be empty" }
+    }
+
+    /** A builder for [GetCredentialRequest]. */
+    class Builder {
+        private var getCredentialOptions: MutableList<GetCredentialOption> = mutableListOf()
+
+        /** Adds a specific type of [GetCredentialOption]. */
+        fun addGetCredentialOption(getCredentialOption: GetCredentialOption): Builder {
+            getCredentialOptions.add(getCredentialOption)
+            return this
+        }
+
+        /** Sets the list of [GetCredentialOption]. */
+        fun setGetCredentialOptions(getCredentialOptions: List<GetCredentialOption>): Builder {
+            this.getCredentialOptions = getCredentialOptions.toMutableList()
+            return this
+        }
+
+        /**
+         * Builds a [GetCredentialRequest].
+         *
+         * @throws IllegalArgumentException If [getCredentialOptions] is empty
+         */
+        fun build(): GetCredentialRequest {
+            return GetCredentialRequest(getCredentialOptions.toList())
+        }
+    }
+
+    companion object {
+        @JvmStatic
+        fun createFrom(from: android.credentials.GetCredentialRequest): GetCredentialRequest {
+            return GetCredentialRequest(
+                    from.getCredentialOptions.map {GetCredentialOption.createFrom(it)}
+            )
+        }
+    }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPasswordOption.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPasswordOption.kt
new file mode 100644
index 0000000..2facad1
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPasswordOption.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2022 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.credentialmanager.jetpack.developer
+
+import android.credentials.Credential
+import android.os.Bundle
+
+/** A request to retrieve the user's saved application password from their password provider. */
+class GetPasswordOption : GetCredentialOption(
+        Credential.TYPE_PASSWORD_CREDENTIAL,
+        Bundle(),
+        false,
+) {
+    companion object {
+        @JvmStatic
+        fun createFrom(data: Bundle): GetPasswordOption {
+            return GetPasswordOption()
+        }
+    }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialBaseOption.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialBaseOption.kt
new file mode 100644
index 0000000..9b51b30
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialBaseOption.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2022 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.credentialmanager.jetpack.developer
+
+import android.os.Bundle
+
+/**
+ * Base request class for getting a registered public key credential.
+ *
+ * @property requestJson the request in JSON format
+ * @throws NullPointerException If [requestJson] is null - auto handled by the
+ * Kotlin runtime
+ * @throws IllegalArgumentException If [requestJson] is empty
+ *
+ * @hide
+ */
+abstract class GetPublicKeyCredentialBaseOption constructor(
+        val requestJson: String,
+        type: String,
+        data: Bundle,
+        requireSystemProvider: Boolean,
+) : GetCredentialOption(type, data, requireSystemProvider) {
+
+    init {
+        require(requestJson.isNotEmpty()) { "request json must not be empty" }
+    }
+
+    companion object {
+        const val BUNDLE_KEY_REQUEST_JSON = "androidx.credentials.BUNDLE_KEY_REQUEST_JSON"
+        const val BUNDLE_KEY_SUBTYPE = "androidx.credentials.BUNDLE_KEY_SUBTYPE"
+
+        @JvmStatic
+        fun createFrom(data: Bundle): GetPublicKeyCredentialBaseOption {
+            return when (data.getString(BUNDLE_KEY_SUBTYPE)) {
+                GetPublicKeyCredentialOption
+                        .BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION ->
+                    GetPublicKeyCredentialOption.createFrom(data)
+                GetPublicKeyCredentialOptionPrivileged
+                        .BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION_PRIVILEGED ->
+                    GetPublicKeyCredentialOptionPrivileged.createFrom(data)
+                else -> throw FrameworkClassParsingException()
+            }
+        }
+    }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialOption.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialOption.kt
new file mode 100644
index 0000000..6f13c17
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialOption.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 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.credentialmanager.jetpack.developer
+
+import android.os.Bundle
+
+/**
+ * A request to get passkeys from the user's public key credential provider.
+ *
+ * @property requestJson the request in JSON format
+ * @property allowHybrid defines whether hybrid credentials are allowed to fulfill this request,
+ * true by default
+ * @throws NullPointerException If [requestJson] or [allowHybrid] is null. It is handled by the
+ * Kotlin runtime
+ * @throws IllegalArgumentException If [requestJson] is empty
+ *
+ * @hide
+ */
+class GetPublicKeyCredentialOption @JvmOverloads constructor(
+        requestJson: String,
+        @get:JvmName("allowHybrid")
+        val allowHybrid: Boolean = true,
+) : GetPublicKeyCredentialBaseOption(
+        requestJson,
+        PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL,
+        toBundle(requestJson, allowHybrid),
+        false
+) {
+    companion object {
+        const val BUNDLE_KEY_ALLOW_HYBRID = "androidx.credentials.BUNDLE_KEY_ALLOW_HYBRID"
+        const val BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION =
+                "androidx.credentials.BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION"
+
+        @JvmStatic
+        internal fun toBundle(requestJson: String, allowHybrid: Boolean): Bundle {
+            val bundle = Bundle()
+            bundle.putString(BUNDLE_KEY_REQUEST_JSON, requestJson)
+            bundle.putBoolean(BUNDLE_KEY_ALLOW_HYBRID, allowHybrid)
+            return bundle
+        }
+
+        @JvmStatic
+        fun createFrom(data: Bundle): GetPublicKeyCredentialOption {
+            try {
+                val requestJson = data.getString(BUNDLE_KEY_REQUEST_JSON)
+                val allowHybrid = data.get(BUNDLE_KEY_ALLOW_HYBRID)
+                return GetPublicKeyCredentialOption(requestJson!!, (allowHybrid!!) as Boolean)
+            } catch (e: Exception) {
+                throw FrameworkClassParsingException()
+            }
+        }
+    }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialOptionPrivileged.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialOptionPrivileged.kt
new file mode 100644
index 0000000..79c62a1
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/GetPublicKeyCredentialOptionPrivileged.kt
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2022 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.credentialmanager.jetpack.developer
+
+import android.os.Bundle
+
+/**
+ * A privileged request to get passkeys from the user's public key credential provider. The caller
+ * can modify the RP. Only callers with privileged permission (e.g. user's public browser or caBLE)
+ * can use this.
+ *
+ * @property requestJson the privileged request in JSON format
+ * @property allowHybrid defines whether hybrid credentials are allowed to fulfill this request,
+ * true by default
+ * @property rp the expected true RP ID which will override the one in the [requestJson]
+ * @property clientDataHash a hash that is used to verify the [rp] Identity
+ * @throws NullPointerException If any of [allowHybrid], [requestJson], [rp], or [clientDataHash]
+ * is null. This is handled by the Kotlin runtime
+ * @throws IllegalArgumentException If any of [requestJson], [rp], or [clientDataHash] is empty
+ *
+ * @hide
+ */
+class GetPublicKeyCredentialOptionPrivileged @JvmOverloads constructor(
+        requestJson: String,
+        val rp: String,
+        val clientDataHash: String,
+        @get:JvmName("allowHybrid")
+        val allowHybrid: Boolean = true
+) : GetPublicKeyCredentialBaseOption(
+        requestJson,
+        PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL,
+        toBundle(requestJson, rp, clientDataHash, allowHybrid),
+        false,
+) {
+
+    init {
+        require(rp.isNotEmpty()) { "rp must not be empty" }
+        require(clientDataHash.isNotEmpty()) { "clientDataHash must not be empty" }
+    }
+
+    /** A builder for [GetPublicKeyCredentialOptionPrivileged]. */
+    class Builder(var requestJson: String, var rp: String, var clientDataHash: String) {
+
+        private var allowHybrid: Boolean = true
+
+        /**
+         * Sets the privileged request in JSON format.
+         */
+        fun setRequestJson(requestJson: String): Builder {
+            this.requestJson = requestJson
+            return this
+        }
+
+        /**
+         * Sets whether hybrid credentials are allowed to fulfill this request, true by default.
+         */
+        fun setAllowHybrid(allowHybrid: Boolean): Builder {
+            this.allowHybrid = allowHybrid
+            return this
+        }
+
+        /**
+         * Sets the expected true RP ID which will override the one in the [requestJson].
+         */
+        fun setRp(rp: String): Builder {
+            this.rp = rp
+            return this
+        }
+
+        /**
+         * Sets a hash that is used to verify the [rp] Identity.
+         */
+        fun setClientDataHash(clientDataHash: String): Builder {
+            this.clientDataHash = clientDataHash
+            return this
+        }
+
+        /** Builds a [GetPublicKeyCredentialOptionPrivileged]. */
+        fun build(): GetPublicKeyCredentialOptionPrivileged {
+            return GetPublicKeyCredentialOptionPrivileged(this.requestJson,
+                    this.rp, this.clientDataHash, this.allowHybrid)
+        }
+    }
+
+    companion object {
+        const val BUNDLE_KEY_RP = "androidx.credentials.BUNDLE_KEY_RP"
+        const val BUNDLE_KEY_CLIENT_DATA_HASH =
+                "androidx.credentials.BUNDLE_KEY_CLIENT_DATA_HASH"
+        const val BUNDLE_KEY_ALLOW_HYBRID = "androidx.credentials.BUNDLE_KEY_ALLOW_HYBRID"
+        const val BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION_PRIVILEGED =
+                "androidx.credentials.BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION" +
+                        "_PRIVILEGED"
+
+        @JvmStatic
+        internal fun toBundle(
+                requestJson: String,
+                rp: String,
+                clientDataHash: String,
+                allowHybrid: Boolean
+        ): Bundle {
+            val bundle = Bundle()
+            bundle.putString(BUNDLE_KEY_REQUEST_JSON, requestJson)
+            bundle.putString(BUNDLE_KEY_RP, rp)
+            bundle.putString(BUNDLE_KEY_CLIENT_DATA_HASH, clientDataHash)
+            bundle.putBoolean(BUNDLE_KEY_ALLOW_HYBRID, allowHybrid)
+            return bundle
+        }
+
+        @JvmStatic
+        fun createFrom(data: Bundle): GetPublicKeyCredentialOptionPrivileged {
+            try {
+                val requestJson = data.getString(BUNDLE_KEY_REQUEST_JSON)
+                val rp = data.getString(BUNDLE_KEY_RP)
+                val clientDataHash = data.getString(BUNDLE_KEY_CLIENT_DATA_HASH)
+                val allowHybrid = data.get(BUNDLE_KEY_ALLOW_HYBRID)
+                return GetPublicKeyCredentialOptionPrivileged(
+                        requestJson!!,
+                        rp!!,
+                        clientDataHash!!,
+                        (allowHybrid!!) as Boolean,
+                )
+            } catch (e: Exception) {
+                throw FrameworkClassParsingException()
+            }
+        }
+    }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/PublicKeyCredential.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/PublicKeyCredential.kt
new file mode 100644
index 0000000..b45a63b
--- /dev/null
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/developer/PublicKeyCredential.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2022 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.credentialmanager.jetpack.developer
+
+import android.os.Bundle
+
+/**
+ * Represents the user's passkey credential granted by the user for app sign-in.
+ *
+ * @property authenticationResponseJson the public key credential authentication response in
+ * JSON format that follows the standard webauthn json format shown at
+ * [this w3c link](https://w3c.github.io/webauthn/#dictdef-authenticationresponsejson)
+ * @throws NullPointerException If [authenticationResponseJson] is null. This is handled by the
+ * kotlin runtime
+ * @throws IllegalArgumentException If [authenticationResponseJson] is empty
+ *
+ * @hide
+ */
+class PublicKeyCredential constructor(
+        val authenticationResponseJson: String
+) : Credential(
+        TYPE_PUBLIC_KEY_CREDENTIAL,
+        toBundle(authenticationResponseJson)
+) {
+
+    init {
+        require(authenticationResponseJson.isNotEmpty()) {
+            "authentication response JSON must not be empty" }
+    }
+    companion object {
+        /** The type value for public key credential related operations. */
+        const val TYPE_PUBLIC_KEY_CREDENTIAL: String =
+                "androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL"
+        const val BUNDLE_KEY_AUTHENTICATION_RESPONSE_JSON =
+                "androidx.credentials.BUNDLE_KEY_AUTHENTICATION_RESPONSE_JSON"
+
+        @JvmStatic
+        internal fun toBundle(authenticationResponseJson: String): Bundle {
+            val bundle = Bundle()
+            bundle.putString(BUNDLE_KEY_AUTHENTICATION_RESPONSE_JSON, authenticationResponseJson)
+            return bundle
+        }
+    }
+}
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/ActionUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/ActionUi.kt
similarity index 96%
rename from packages/CredentialManager/src/com/android/credentialmanager/jetpack/ActionUi.kt
rename to packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/ActionUi.kt
index d4341b4..1e639fe 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/ActionUi.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/ActionUi.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.credentialmanager.jetpack
+package com.android.credentialmanager.jetpack.provider
 
 import android.app.slice.Slice
 import android.credentials.ui.Entry
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/CredentialEntryUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/CredentialEntryUi.kt
similarity index 96%
rename from packages/CredentialManager/src/com/android/credentialmanager/jetpack/CredentialEntryUi.kt
rename to packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/CredentialEntryUi.kt
index d6f1b5f..12ab436 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/CredentialEntryUi.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/CredentialEntryUi.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.credentialmanager.jetpack
+package com.android.credentialmanager.jetpack.provider
 
 import android.app.slice.Slice
 import android.graphics.drawable.Icon
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/PasskeyCredentialEntryUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/PasskeyCredentialEntryUi.kt
similarity index 97%
rename from packages/CredentialManager/src/com/android/credentialmanager/jetpack/PasskeyCredentialEntryUi.kt
rename to packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/PasskeyCredentialEntryUi.kt
index bb3b206..c5dbe66 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/PasskeyCredentialEntryUi.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/PasskeyCredentialEntryUi.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.credentialmanager.jetpack
+package com.android.credentialmanager.jetpack.provider
 
 import android.app.slice.Slice
 import android.credentials.ui.Entry
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/PasswordCredentialEntryUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/PasswordCredentialEntryUi.kt
similarity index 97%
rename from packages/CredentialManager/src/com/android/credentialmanager/jetpack/PasswordCredentialEntryUi.kt
rename to packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/PasswordCredentialEntryUi.kt
index 7311b70..5049503 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/PasswordCredentialEntryUi.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/PasswordCredentialEntryUi.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.credentialmanager.jetpack
+package com.android.credentialmanager.jetpack.provider
 
 import android.app.slice.Slice
 import android.credentials.ui.Entry
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/SaveEntryUi.kt b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/SaveEntryUi.kt
similarity index 97%
rename from packages/CredentialManager/src/com/android/credentialmanager/jetpack/SaveEntryUi.kt
rename to packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/SaveEntryUi.kt
index fad3309..b260cf6 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/jetpack/SaveEntryUi.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/jetpack/provider/SaveEntryUi.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.credentialmanager.jetpack
+package com.android.credentialmanager.jetpack.provider
 
 import android.app.slice.Slice
 import android.credentials.ui.Entry
diff --git a/packages/SettingsLib/Android.bp b/packages/SettingsLib/Android.bp
index c659525..f170ead 100644
--- a/packages/SettingsLib/Android.bp
+++ b/packages/SettingsLib/Android.bp
@@ -54,6 +54,7 @@
         "SettingsLibSettingsTransition",
         "SettingsLibButtonPreference",
         "SettingsLibDeviceStateRotationLock",
+        "SettingsLibProfileSelector",
         "setupdesign",
         "zxing-core-1.7",
         "androidx.room_room-runtime",
diff --git a/packages/SettingsLib/ProfileSelector/Android.bp b/packages/SettingsLib/ProfileSelector/Android.bp
new file mode 100644
index 0000000..250cd75
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/Android.bp
@@ -0,0 +1,27 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_library {
+    name: "SettingsLibProfileSelector",
+
+    srcs: ["src/**/*.java"],
+    resource_dirs: ["res"],
+
+    static_libs: [
+        "com.google.android.material_material",
+        "SettingsLibSettingsTheme",
+    ],
+
+    sdk_version: "system_current",
+    min_sdk_version: "23",
+    apex_available: [
+        "//apex_available:platform",
+        "com.android.mediaprovider",
+    ],
+}
diff --git a/packages/SettingsLib/Spa/spa/res/values-night/themes.xml b/packages/SettingsLib/ProfileSelector/AndroidManifest.xml
similarity index 70%
rename from packages/SettingsLib/Spa/spa/res/values-night/themes.xml
rename to packages/SettingsLib/ProfileSelector/AndroidManifest.xml
index 67dd2b0..a57469e 100644
--- a/packages/SettingsLib/Spa/spa/res/values-night/themes.xml
+++ b/packages/SettingsLib/ProfileSelector/AndroidManifest.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  Copyright (C) 2022 The Android Open Source Project
+  Copyright (C) 2020 The Android Open Source Project
 
   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
@@ -13,8 +13,10 @@
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
--->
-<resources>
+  -->
 
-    <style name="Theme.SpaLib.DayNight" />
-</resources>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.settingslib.widget">
+
+    <uses-sdk android:minSdkVersion="23" />
+</manifest>
diff --git a/packages/SettingsLib/res/color-night-v31/settingslib_tabs_indicator_color.xml b/packages/SettingsLib/ProfileSelector/res/color-night/settingslib_tabs_indicator_color.xml
similarity index 100%
rename from packages/SettingsLib/res/color-night-v31/settingslib_tabs_indicator_color.xml
rename to packages/SettingsLib/ProfileSelector/res/color-night/settingslib_tabs_indicator_color.xml
diff --git a/packages/SettingsLib/res/color-night-v31/settingslib_tabs_text_color.xml b/packages/SettingsLib/ProfileSelector/res/color-night/settingslib_tabs_text_color.xml
similarity index 100%
rename from packages/SettingsLib/res/color-night-v31/settingslib_tabs_text_color.xml
rename to packages/SettingsLib/ProfileSelector/res/color-night/settingslib_tabs_text_color.xml
diff --git a/packages/SettingsLib/res/color-v31/settingslib_tabs_indicator_color.xml b/packages/SettingsLib/ProfileSelector/res/color/settingslib_tabs_indicator_color.xml
similarity index 100%
rename from packages/SettingsLib/res/color-v31/settingslib_tabs_indicator_color.xml
rename to packages/SettingsLib/ProfileSelector/res/color/settingslib_tabs_indicator_color.xml
diff --git a/packages/SettingsLib/res/color-v31/settingslib_tabs_text_color.xml b/packages/SettingsLib/ProfileSelector/res/color/settingslib_tabs_text_color.xml
similarity index 100%
rename from packages/SettingsLib/res/color-v31/settingslib_tabs_text_color.xml
rename to packages/SettingsLib/ProfileSelector/res/color/settingslib_tabs_text_color.xml
diff --git a/packages/SettingsLib/res/drawable-v31/settingslib_tabs_background.xml b/packages/SettingsLib/ProfileSelector/res/drawable/settingslib_tabs_background.xml
similarity index 100%
rename from packages/SettingsLib/res/drawable-v31/settingslib_tabs_background.xml
rename to packages/SettingsLib/ProfileSelector/res/drawable/settingslib_tabs_background.xml
diff --git a/packages/SettingsLib/res/drawable-v31/settingslib_tabs_indicator_background.xml b/packages/SettingsLib/ProfileSelector/res/drawable/settingslib_tabs_indicator_background.xml
similarity index 100%
rename from packages/SettingsLib/res/drawable-v31/settingslib_tabs_indicator_background.xml
rename to packages/SettingsLib/ProfileSelector/res/drawable/settingslib_tabs_indicator_background.xml
diff --git a/packages/SettingsLib/ProfileSelector/res/layout/tab_fragment.xml b/packages/SettingsLib/ProfileSelector/res/layout/tab_fragment.xml
new file mode 100644
index 0000000..0448c6c
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/res/layout/tab_fragment.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright (C) 2022 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:theme="@style/Theme.MaterialComponents.DayNight"
+    android:id="@+id/tab_container"
+    android:clipToPadding="true"
+    android:clipChildren="true"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+
+    <com.google.android.material.tabs.TabLayout
+        android:id="@+id/tabs"
+        style="@style/SettingsLibTabsStyle"/>
+
+    <androidx.viewpager2.widget.ViewPager2
+        android:id="@+id/view_pager"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+    </androidx.viewpager2.widget.ViewPager2>
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/spa/res/values-night/themes.xml b/packages/SettingsLib/ProfileSelector/res/values/strings.xml
similarity index 65%
copy from packages/SettingsLib/Spa/spa/res/values-night/themes.xml
copy to packages/SettingsLib/ProfileSelector/res/values/strings.xml
index 67dd2b0..68d4047 100644
--- a/packages/SettingsLib/Spa/spa/res/values-night/themes.xml
+++ b/packages/SettingsLib/ProfileSelector/res/values/strings.xml
@@ -13,8 +13,12 @@
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
--->
-<resources>
+  -->
 
-    <style name="Theme.SpaLib.DayNight" />
-</resources>
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+    <!-- Header for items under the personal user [CHAR LIMIT=30] -->
+    <string name="settingslib_category_personal">Personal</string>
+    <!-- Header for items under the work user [CHAR LIMIT=30] -->
+    <string name="settingslib_category_work">Work</string>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/res/values-v31/styles.xml b/packages/SettingsLib/ProfileSelector/res/values/styles.xml
similarity index 100%
rename from packages/SettingsLib/res/values-v31/styles.xml
rename to packages/SettingsLib/ProfileSelector/res/values/styles.xml
diff --git a/packages/SettingsLib/ProfileSelector/src/com/android/settingslib/widget/ProfileSelectFragment.java b/packages/SettingsLib/ProfileSelector/src/com/android/settingslib/widget/ProfileSelectFragment.java
new file mode 100644
index 0000000..ac426ed
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/src/com/android/settingslib/widget/ProfileSelectFragment.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 2022 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.settingslib.widget;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.fragment.app.Fragment;
+import androidx.viewpager2.widget.ViewPager2;
+
+import com.google.android.material.tabs.TabLayout;
+import com.google.android.material.tabs.TabLayoutMediator;
+
+/**
+ * Base fragment class for profile settings.
+ */
+public abstract class ProfileSelectFragment extends Fragment {
+
+    /**
+     * Personal or Work profile tab of {@link ProfileSelectFragment}
+     * <p>0: Personal tab.
+     * <p>1: Work profile tab.
+     */
+    public static final String EXTRA_SHOW_FRAGMENT_TAB =
+            ":settings:show_fragment_tab";
+
+    /**
+     * Used in fragment argument with Extra key EXTRA_SHOW_FRAGMENT_TAB
+     */
+    public static final int PERSONAL_TAB = 0;
+
+    /**
+     * Used in fragment argument with Extra key EXTRA_SHOW_FRAGMENT_TAB
+     */
+    public static final int WORK_TAB = 1;
+
+    private ViewGroup mContentView;
+
+    private ViewPager2 mViewPager;
+
+    @Override
+    public View onCreateView(LayoutInflater inflater, ViewGroup container,
+            Bundle savedInstanceState) {
+        // Defines the xml file for the fragment
+        mContentView = (ViewGroup) inflater.inflate(R.layout.tab_fragment, container, false);
+
+        final Activity activity = getActivity();
+        final int titleResId = getTitleResId();
+        if (titleResId > 0) {
+            activity.setTitle(titleResId);
+        }
+        final int selectedTab = getTabId(activity, getArguments());
+
+        final View tabContainer = mContentView.findViewById(R.id.tab_container);
+        mViewPager = tabContainer.findViewById(R.id.view_pager);
+        mViewPager.setAdapter(new ProfileViewPagerAdapter(this));
+        final TabLayout tabs = tabContainer.findViewById(R.id.tabs);
+        new TabLayoutMediator(tabs, mViewPager,
+                (tab, position) -> tab.setText(getPageTitle(position))
+        ).attach();
+
+        tabContainer.setVisibility(View.VISIBLE);
+        final TabLayout.Tab tab = tabs.getTabAt(selectedTab);
+        tab.select();
+
+        return mContentView;
+    }
+
+    /**
+     * create Personal or Work profile fragment
+     * <p>0: Personal profile.
+     * <p>1: Work profile.
+     */
+    public abstract Fragment createFragment(int position);
+
+    /**
+     * Returns a resource ID of the title
+     * Override this if the title needs to be updated dynamically.
+     */
+    public int getTitleResId() {
+        return 0;
+    }
+
+    int getTabId(Activity activity, Bundle bundle) {
+        if (bundle != null) {
+            final int extraTab = bundle.getInt(EXTRA_SHOW_FRAGMENT_TAB, -1);
+            if (extraTab != -1) {
+                return extraTab;
+            }
+        }
+        return PERSONAL_TAB;
+    }
+
+    private CharSequence getPageTitle(int position) {
+        if (position == WORK_TAB) {
+            return getContext().getString(R.string.settingslib_category_work);
+        }
+
+        return getString(R.string.settingslib_category_personal);
+    }
+}
diff --git a/packages/SettingsLib/ProfileSelector/src/com/android/settingslib/widget/ProfileViewPagerAdapter.java b/packages/SettingsLib/ProfileSelector/src/com/android/settingslib/widget/ProfileViewPagerAdapter.java
new file mode 100644
index 0000000..daf2564
--- /dev/null
+++ b/packages/SettingsLib/ProfileSelector/src/com/android/settingslib/widget/ProfileViewPagerAdapter.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2022 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.settingslib.widget;
+
+import androidx.fragment.app.Fragment;
+import androidx.viewpager2.adapter.FragmentStateAdapter;
+
+/**
+ * ViewPager Adapter to handle between TabLayout and ViewPager2
+ */
+public class ProfileViewPagerAdapter extends FragmentStateAdapter {
+
+    private final ProfileSelectFragment mParentFragments;
+
+    ProfileViewPagerAdapter(ProfileSelectFragment fragment) {
+        super(fragment);
+        mParentFragments = fragment;
+    }
+
+    @Override
+    public Fragment createFragment(int position) {
+        return mParentFragments.createFragment(position);
+    }
+
+    @Override
+    public int getItemCount() {
+        return 2;
+    }
+}
diff --git a/packages/SettingsLib/SelectorWithWidgetPreference/Android.bp b/packages/SettingsLib/SelectorWithWidgetPreference/Android.bp
index bcc64d3..b5a21bd 100644
--- a/packages/SettingsLib/SelectorWithWidgetPreference/Android.bp
+++ b/packages/SettingsLib/SelectorWithWidgetPreference/Android.bp
@@ -23,5 +23,6 @@
     apex_available: [
         "//apex_available:platform",
         "com.android.permission",
+        "com.android.mediaprovider",
     ],
 }
diff --git a/packages/SettingsLib/SettingsTheme/Android.bp b/packages/SettingsLib/SettingsTheme/Android.bp
index 82e0220..939977f 100644
--- a/packages/SettingsLib/SettingsTheme/Android.bp
+++ b/packages/SettingsLib/SettingsTheme/Android.bp
@@ -24,5 +24,6 @@
         "com.android.permission",
         "com.android.adservices",
         "com.android.healthconnect",
+        "com.android.mediaprovider",
     ],
 }
diff --git a/packages/SettingsLib/Spa/gallery/build.gradle b/packages/SettingsLib/Spa/gallery/build.gradle
index 20dd707..e04a9be 100644
--- a/packages/SettingsLib/Spa/gallery/build.gradle
+++ b/packages/SettingsLib/Spa/gallery/build.gradle
@@ -57,5 +57,5 @@
 }
 
 dependencies {
-    implementation(project(":spa"))
+    implementation project(":spa")
 }
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryApplication.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryApplication.kt
index 36b58ad..dfbf244 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryApplication.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GalleryApplication.kt
@@ -22,6 +22,6 @@
 class GalleryApplication : Application() {
     override fun onCreate() {
         super.onCreate()
-        SpaEnvironmentFactory.reset(GallerySpaEnvironment)
+        SpaEnvironmentFactory.reset(GallerySpaEnvironment(this))
     }
 }
\ No newline at end of file
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
index 4af2589..742e271 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
@@ -16,6 +16,7 @@
 
 package com.android.settingslib.spa.gallery
 
+import android.content.Context
 import com.android.settingslib.spa.framework.common.LocalLogger
 import com.android.settingslib.spa.framework.common.SettingsPageProviderRepository
 import com.android.settingslib.spa.framework.common.SpaEnvironment
@@ -23,6 +24,7 @@
 import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider
 import com.android.settingslib.spa.gallery.home.HomePageProvider
 import com.android.settingslib.spa.gallery.page.ArgumentPageProvider
+import com.android.settingslib.spa.gallery.page.ChartPageProvider
 import com.android.settingslib.spa.gallery.page.FooterPageProvider
 import com.android.settingslib.spa.gallery.page.IllustrationPageProvider
 import com.android.settingslib.spa.gallery.page.ProgressBarPageProvider
@@ -49,7 +51,7 @@
     // Add your SPPs
 }
 
-object GallerySpaEnvironment : SpaEnvironment() {
+class GallerySpaEnvironment(context: Context) : SpaEnvironment(context) {
     override val pageProviderRepository = lazy {
         SettingsPageProviderRepository(
             allPageProviders = listOf(
@@ -68,6 +70,7 @@
                 CategoryPageProvider,
                 ActionButtonPageProvider,
                 ProgressBarPageProvider,
+                ChartPageProvider,
             ),
             rootPages = listOf(
                 HomePageProvider.createSettingsPage(),
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt
index 7fd49db..83c72c7 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePage.kt
@@ -18,10 +18,10 @@
 
 import android.os.Bundle
 import androidx.compose.runtime.Composable
-import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.tooling.preview.Preview
 import com.android.settingslib.spa.framework.common.SettingsEntry
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
 import com.android.settingslib.spa.framework.common.createSettingsPage
 import com.android.settingslib.spa.framework.theme.SettingsTheme
 import com.android.settingslib.spa.gallery.R
@@ -29,6 +29,7 @@
 import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider
 import com.android.settingslib.spa.gallery.page.ArgumentPageModel
 import com.android.settingslib.spa.gallery.page.ArgumentPageProvider
+import com.android.settingslib.spa.gallery.page.ChartPageProvider
 import com.android.settingslib.spa.gallery.page.FooterPageProvider
 import com.android.settingslib.spa.gallery.page.IllustrationPageProvider
 import com.android.settingslib.spa.gallery.page.ProgressBarPageProvider
@@ -56,12 +57,17 @@
             CategoryPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
             ActionButtonPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
             ProgressBarPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
+            ChartPageProvider.buildInjectEntry().setLink(fromPage = owner).build(),
         )
     }
 
+    override fun getTitle(arguments: Bundle?): String {
+        return SpaEnvironmentFactory.instance.appContext.getString(R.string.app_name)
+    }
+
     @Composable
     override fun Page(arguments: Bundle?) {
-        HomeScaffold(title = stringResource(R.string.app_name)) {
+        HomeScaffold(title = getTitle(arguments)) {
             for (entry in buildEntry(arguments)) {
                 if (entry.owner.isCreateBy(SettingsPageProviderEnum.ARGUMENT.name)) {
                     entry.UiLayout(ArgumentPageModel.buildArgument(intParam = 0))
@@ -76,6 +82,7 @@
 @Preview(showBackground = true)
 @Composable
 private fun HomeScreenPreview() {
+    SpaEnvironmentFactory.resetForPreview()
     SettingsTheme {
         HomePageProvider.Page(null)
     }
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
index 8207310..7958d11 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPage.kt
@@ -23,6 +23,7 @@
 import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
 import com.android.settingslib.spa.framework.common.SettingsPage
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.common.SpaEnvironmentFactory
 import com.android.settingslib.spa.framework.common.createSettingsPage
 import com.android.settingslib.spa.framework.theme.SettingsTheme
 import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
@@ -98,9 +99,13 @@
             }
     }
 
+    override fun getTitle(arguments: Bundle?): String {
+        return ArgumentPageModel.genPageTitle()
+    }
+
     @Composable
     override fun Page(arguments: Bundle?) {
-        RegularScaffold(title = ArgumentPageModel.create(arguments).genPageTitle()) {
+        RegularScaffold(title = getTitle(arguments)) {
             for (entry in buildEntry(arguments)) {
                 if (entry.toPage != null) {
                     entry.UiLayout(ArgumentPageModel.buildNextArgument(arguments))
@@ -115,6 +120,7 @@
 @Preview(showBackground = true)
 @Composable
 private fun ArgumentPagePreview() {
+    SpaEnvironmentFactory.resetForPreview()
     SettingsTheme {
         ArgumentPageProvider.Page(
             ArgumentPageModel.buildArgument(stringParam = "foo", intParam = 0)
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt
index e5e3c67..5f15865 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ArgumentPageModel.kt
@@ -81,6 +81,10 @@
             return EntrySearchData(title = PAGE_TITLE, keyword = ARGUMENT_PAGE_KEYWORDS)
         }
 
+        fun genPageTitle(): String {
+            return PAGE_TITLE
+        }
+
         @Composable
         fun create(arguments: Bundle?): ArgumentPageModel {
             val pageModel: ArgumentPageModel = viewModel(key = arguments.toString())
@@ -89,7 +93,6 @@
         }
     }
 
-    private val title = PAGE_TITLE
     private var arguments: Bundle? = null
     private var stringParam: String? = null
     private var intParam: Int? = null
@@ -104,11 +107,6 @@
     }
 
     @Composable
-    fun genPageTitle(): String {
-        return title
-    }
-
-    @Composable
     fun genStringParamPreferenceModel(): PreferenceModel {
         return object : PreferenceModel {
             override val title = STRING_PARAM_TITLE
@@ -131,7 +129,7 @@
             "$INT_PARAM_NAME=" + intParam!!
         )
         return object : PreferenceModel {
-            override val title = genPageTitle()
+            override val title = PAGE_TITLE
             override val summary = stateOf(summaryArray.joinToString(", "))
             override val onClick = navigator(
                 SettingsPageProviderEnum.ARGUMENT.displayName + parameter.navLink(arguments)
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ChartPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ChartPage.kt
new file mode 100644
index 0000000..160e77b
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ChartPage.kt
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.gallery.page
+
+import android.os.Bundle
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.common.SettingsEntry
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPage
+import com.android.settingslib.spa.framework.common.SettingsPageProvider
+import com.android.settingslib.spa.framework.compose.navigator
+import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.chart.BarChart
+import com.android.settingslib.spa.widget.chart.BarChartData
+import com.android.settingslib.spa.widget.chart.BarChartModel
+import com.android.settingslib.spa.widget.chart.LineChart
+import com.android.settingslib.spa.widget.chart.LineChartData
+import com.android.settingslib.spa.widget.chart.LineChartModel
+import com.android.settingslib.spa.widget.chart.PieChart
+import com.android.settingslib.spa.widget.chart.PieChartData
+import com.android.settingslib.spa.widget.chart.PieChartModel
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
+import com.github.mikephil.charting.formatter.IAxisValueFormatter
+import java.text.NumberFormat
+
+private enum class WeekDay(val num: Int) {
+    Sun(0), Mon(1), Tue(2), Wed(3), Thu(4), Fri(5), Sat(6),
+}
+private const val TITLE = "Sample Chart"
+
+object ChartPageProvider : SettingsPageProvider {
+    override val name = "Chart"
+
+    override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+        val owner = SettingsPage.create(name)
+        val entryList = mutableListOf<SettingsEntry>()
+        entryList.add(
+            SettingsEntryBuilder.create("Line Chart", owner)
+                .setUiLayoutFn {
+                    Preference(object : PreferenceModel {
+                        override val title = "Line Chart"
+                    })
+                    LineChart(
+                        lineChartModel = object : LineChartModel {
+                            override val chartDataList = listOf(
+                                LineChartData(x = 0f, y = 0f),
+                                LineChartData(x = 1f, y = 0.1f),
+                                LineChartData(x = 2f, y = 0.2f),
+                                LineChartData(x = 3f, y = 0.6f),
+                                LineChartData(x = 4f, y = 0.9f),
+                                LineChartData(x = 5f, y = 1.0f),
+                                LineChartData(x = 6f, y = 0.8f),
+                            )
+                            override val xValueFormatter =
+                                IAxisValueFormatter { value, _ ->
+                                    "${WeekDay.values()[value.toInt()]}"
+                                }
+                            override val yValueFormatter =
+                                IAxisValueFormatter { value, _ ->
+                                    NumberFormat.getPercentInstance().format(value)
+                                }
+                        }
+                    )
+                }.build()
+        )
+        entryList.add(
+            SettingsEntryBuilder.create("Bar Chart", owner)
+                .setUiLayoutFn {
+                    Preference(object : PreferenceModel {
+                        override val title = "Bar Chart"
+                    })
+                    BarChart(
+                        barChartModel = object : BarChartModel {
+                            override val chartDataList = listOf(
+                                BarChartData(x = 0f, y = 12f),
+                                BarChartData(x = 1f, y = 5f),
+                                BarChartData(x = 2f, y = 21f),
+                                BarChartData(x = 3f, y = 5f),
+                                BarChartData(x = 4f, y = 10f),
+                                BarChartData(x = 5f, y = 9f),
+                                BarChartData(x = 6f, y = 1f),
+                            )
+                            override val xValueFormatter =
+                                IAxisValueFormatter { value, _ ->
+                                    "${WeekDay.values()[value.toInt()]}"
+                                }
+                            override val yValueFormatter =
+                                IAxisValueFormatter { value, _ ->
+                                    "${value.toInt()}m"
+                                }
+                            override val yAxisMaxValue = 30f
+                        }
+                    )
+                }.build()
+        )
+        entryList.add(
+            SettingsEntryBuilder.create("Pie Chart", owner)
+                .setUiLayoutFn {
+                    Preference(object : PreferenceModel {
+                        override val title = "Pie Chart"
+                    })
+                    PieChart(
+                        pieChartModel = object : PieChartModel {
+                            override val chartDataList = listOf(
+                                PieChartData(label = "Settings", value = 20f),
+                                PieChartData(label = "Chrome", value = 5f),
+                                PieChartData(label = "Gmail", value = 3f),
+                            )
+                            override val centerText = "Today"
+                        }
+                    )
+                }.build()
+        )
+
+        return entryList
+    }
+
+    fun buildInjectEntry(): SettingsEntryBuilder {
+        return SettingsEntryBuilder.createInject(owner = SettingsPage.create(name))
+            .setIsAllowSearch(true)
+            .setUiLayoutFn {
+                Preference(object : PreferenceModel {
+                    override val title = TITLE
+                    override val onClick = navigator(name)
+                })
+            }
+    }
+
+    @Composable
+    override fun Page(arguments: Bundle?) {
+        RegularScaffold(title = TITLE) {
+            for (entry in buildEntry(arguments)) {
+                entry.UiLayout(arguments)
+            }
+        }
+    }
+}
+
+@Preview(showBackground = true)
+@Composable
+private fun ChartPagePreview() {
+    SettingsTheme {
+        ChartPageProvider.Page(null)
+    }
+}
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt
index 0fc2a5f..c903cfd 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/FooterPage.kt
@@ -67,9 +67,13 @@
             }
     }
 
+    override fun getTitle(arguments: Bundle?): String {
+        return TITLE
+    }
+
     @Composable
     override fun Page(arguments: Bundle?) {
-        RegularScaffold(title = TITLE) {
+        RegularScaffold(title = getTitle(arguments)) {
             for (entry in buildEntry(arguments)) {
                 entry.UiLayout()
             }
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPage.kt
index a64d4a5..e10cf3a 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/IllustrationPage.kt
@@ -31,7 +31,6 @@
 import com.android.settingslib.spa.widget.ResourceType
 import com.android.settingslib.spa.widget.preference.Preference
 import com.android.settingslib.spa.widget.preference.PreferenceModel
-import com.android.settingslib.spa.widget.scaffold.RegularScaffold
 
 private const val TITLE = "Sample Illustration"
 
@@ -82,13 +81,8 @@
             }
     }
 
-    @Composable
-    override fun Page(arguments: Bundle?) {
-        RegularScaffold(title = TITLE) {
-            for (entry in buildEntry(arguments)) {
-                entry.UiLayout()
-            }
-        }
+    override fun getTitle(arguments: Bundle?): String {
+        return TITLE
     }
 }
 
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ProgressBarPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ProgressBarPage.kt
index dc45df4..9136b04 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ProgressBarPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ProgressBarPage.kt
@@ -60,6 +60,10 @@
             }
     }
 
+    override fun getTitle(arguments: Bundle?): String {
+        return TITLE
+    }
+
     @Composable
     override fun Page(arguments: Bundle?) {
         // Mocks a loading time of 2 seconds.
@@ -69,7 +73,7 @@
             loading = false
         }
 
-        RegularScaffold(title = TITLE) {
+        RegularScaffold(title = getTitle(arguments)) {
             // Auto update the progress and finally jump tp 0.4f.
             var progress by remember { mutableStateOf(0f) }
             LaunchedEffect(Unit) {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt
index b38178b..cb58a95 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SettingsPagerPage.kt
@@ -49,9 +49,13 @@
             }
     }
 
+    override fun getTitle(arguments: Bundle?): String {
+        return TITLE
+    }
+
     @Composable
     override fun Page(arguments: Bundle?) {
-        SettingsScaffold(title = TITLE) { paddingValues ->
+        SettingsScaffold(title = getTitle(arguments)) { paddingValues ->
             Box(Modifier.padding(paddingValues)) {
                 SettingsPager(listOf("Personal", "Work")) {
                     PlaceholderTitle("Page $it")
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt
index 7567c6d..73b34a5 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/SliderPage.kt
@@ -37,7 +37,6 @@
 import com.android.settingslib.spa.widget.preference.SliderPreferenceModel
 import com.android.settingslib.spa.widget.preference.Preference
 import com.android.settingslib.spa.widget.preference.PreferenceModel
-import com.android.settingslib.spa.widget.scaffold.RegularScaffold
 
 private const val TITLE = "Sample Slider"
 
@@ -119,13 +118,8 @@
             }
     }
 
-    @Composable
-    override fun Page(arguments: Bundle?) {
-        RegularScaffold(title = TITLE) {
-            for (entry in buildEntry(arguments)) {
-                entry.UiLayout()
-            }
-        }
+    override fun getTitle(arguments: Bundle?): String {
+        return TITLE
     }
 }
 
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/MainSwitchPreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/MainSwitchPreferencePage.kt
index a8e4938..f38a8d4 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/MainSwitchPreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/MainSwitchPreferencePage.kt
@@ -33,7 +33,6 @@
 import com.android.settingslib.spa.widget.preference.Preference
 import com.android.settingslib.spa.widget.preference.PreferenceModel
 import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
-import com.android.settingslib.spa.widget.scaffold.RegularScaffold
 
 private const val TITLE = "Sample MainSwitchPreference"
 
@@ -72,13 +71,8 @@
             }
     }
 
-    @Composable
-    override fun Page(arguments: Bundle?) {
-        RegularScaffold(title = TITLE) {
-            for (entry in buildEntry(arguments)) {
-                entry.UiLayout()
-            }
-        }
+    override fun getTitle(arguments: Bundle?): String {
+        return TITLE
     }
 }
 
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMain.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMain.kt
index 165eaa0..61925a7 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMain.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferenceMain.kt
@@ -17,7 +17,6 @@
 package com.android.settingslib.spa.gallery.preference
 
 import android.os.Bundle
-import androidx.compose.runtime.Composable
 import com.android.settingslib.spa.framework.common.SettingsEntry
 import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
 import com.android.settingslib.spa.framework.common.SettingsPageProvider
@@ -25,7 +24,6 @@
 import com.android.settingslib.spa.framework.compose.navigator
 import com.android.settingslib.spa.widget.preference.Preference
 import com.android.settingslib.spa.widget.preference.PreferenceModel
-import com.android.settingslib.spa.widget.scaffold.RegularScaffold
 
 private const val TITLE = "Category: Preference"
 
@@ -54,12 +52,7 @@
             }
     }
 
-    @Composable
-    override fun Page(arguments: Bundle?) {
-        RegularScaffold(title = TITLE) {
-            for (entry in buildEntry(arguments)) {
-                entry.UiLayout()
-            }
-        }
+    override fun getTitle(arguments: Bundle?): String {
+        return TITLE
     }
 }
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt
index fa8d51c..26e59ff 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePage.kt
@@ -49,7 +49,6 @@
 import com.android.settingslib.spa.widget.preference.Preference
 import com.android.settingslib.spa.widget.preference.PreferenceModel
 import com.android.settingslib.spa.widget.preference.SimplePreferenceMacro
-import com.android.settingslib.spa.widget.scaffold.RegularScaffold
 import com.android.settingslib.spa.widget.ui.SettingsIcon
 
 private const val TAG = "PreferencePage"
@@ -128,11 +127,11 @@
                 .setStatusDataFn { EntryStatusData(isDisabled = false) }
                 .setUiLayoutFn {
                     val model = PreferencePageModel.create()
-                    val asyncSummary = remember { model.getAsyncSummary() }
                     Preference(
                         object : PreferenceModel {
                             override val title = ASYNC_PREFERENCE_TITLE
-                            override val summary = asyncSummary
+                            override val summary = model.asyncSummary
+                            override val enabled = model.asyncEnable
                         }
                     )
                 }.build()
@@ -204,19 +203,15 @@
             }
     }
 
-    @Composable
-    override fun Page(arguments: Bundle?) {
-        RegularScaffold(title = PAGE_TITLE) {
-            for (entry in buildEntry(arguments)) {
-                entry.UiLayout()
-            }
-        }
+    override fun getTitle(arguments: Bundle?): String {
+        return PAGE_TITLE
     }
 }
 
 @Preview(showBackground = true)
 @Composable
 private fun PreferencePagePreview() {
+    SpaEnvironmentFactory.resetForPreview()
     SettingsTheme {
         PreferencePageProvider.Page(null)
     }
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageModel.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageModel.kt
index 1e64b2e..d874417 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageModel.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/PreferencePageModel.kt
@@ -59,7 +59,8 @@
 
     private val spaLogger = SpaEnvironmentFactory.instance.logger
 
-    private val asyncSummary = mutableStateOf(" ")
+    val asyncSummary = mutableStateOf("(loading)")
+    val asyncEnable = mutableStateOf(false)
 
     private val manualUpdater = mutableStateOf(0)
 
@@ -87,16 +88,13 @@
     override fun initialize(arguments: Bundle?) {
         spaLogger.message(TAG, "initialize with args " + arguments.toString())
         viewModelScope.launch(Dispatchers.IO) {
+            // Loading your data here.
             delay(2000L)
             asyncSummary.value = ASYNC_PREFERENCE_SUMMARY
+            asyncEnable.value = true
         }
     }
 
-    fun getAsyncSummary(): State<String> {
-        spaLogger.message(TAG, "getAsyncSummary")
-        return asyncSummary
-    }
-
     fun getManualUpdaterSummary(): State<String> {
         spaLogger.message(TAG, "getManualUpdaterSummary")
         return derivedStateOf { manualUpdater.value.toString() }
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePage.kt
index 46b44ca..367766a 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/SwitchPreferencePage.kt
@@ -34,7 +34,6 @@
 import com.android.settingslib.spa.widget.preference.PreferenceModel
 import com.android.settingslib.spa.widget.preference.SwitchPreference
 import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
-import com.android.settingslib.spa.widget.scaffold.RegularScaffold
 import kotlinx.coroutines.delay
 
 private const val TITLE = "Sample SwitchPreference"
@@ -88,13 +87,8 @@
             }
     }
 
-    @Composable
-    override fun Page(arguments: Bundle?) {
-        RegularScaffold(title = TITLE) {
-            for (entry in buildEntry(arguments)) {
-                entry.UiLayout()
-            }
-        }
+    override fun getTitle(arguments: Bundle?): String {
+        return TITLE
     }
 }
 
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePage.kt
index b991f59..22da99c 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/preference/TwoTargetSwitchPreferencePage.kt
@@ -34,7 +34,6 @@
 import com.android.settingslib.spa.widget.preference.PreferenceModel
 import com.android.settingslib.spa.widget.preference.SwitchPreferenceModel
 import com.android.settingslib.spa.widget.preference.TwoTargetSwitchPreference
-import com.android.settingslib.spa.widget.scaffold.RegularScaffold
 import kotlinx.coroutines.delay
 
 private const val TITLE = "Sample TwoTargetSwitchPreference"
@@ -88,13 +87,8 @@
             }
     }
 
-    @Composable
-    override fun Page(arguments: Bundle?) {
-        RegularScaffold(title = TITLE) {
-            for (entry in buildEntry(arguments)) {
-                entry.UiLayout()
-            }
-        }
+    override fun getTitle(arguments: Bundle?): String {
+        return TITLE
     }
 }
 
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPage.kt
index a4713b9..d87cbe8 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/CategoryPage.kt
@@ -48,41 +48,40 @@
             }
     }
 
+    override fun getTitle(arguments: Bundle?): String {
+        return TITLE
+    }
+
     @Composable
     override fun Page(arguments: Bundle?) {
-        CategoryPage()
-    }
-}
-
-@Composable
-private fun CategoryPage() {
-    RegularScaffold(title = TITLE) {
-        CategoryTitle("Category A")
-        Preference(remember {
-            object : PreferenceModel {
-                override val title = "Preference 1"
-                override val summary = stateOf("Summary 1")
-            }
-        })
-        Preference(remember {
-            object : PreferenceModel {
-                override val title = "Preference 2"
-                override val summary = stateOf("Summary 2")
-            }
-        })
-        Category("Category B") {
+        RegularScaffold(title = getTitle(arguments)) {
+            CategoryTitle("Category A")
             Preference(remember {
                 object : PreferenceModel {
-                    override val title = "Preference 3"
-                    override val summary = stateOf("Summary 3")
+                    override val title = "Preference 1"
+                    override val summary = stateOf("Summary 1")
                 }
             })
             Preference(remember {
                 object : PreferenceModel {
-                    override val title = "Preference 4"
-                    override val summary = stateOf("Summary 4")
+                    override val title = "Preference 2"
+                    override val summary = stateOf("Summary 2")
                 }
             })
+            Category("Category B") {
+                Preference(remember {
+                    object : PreferenceModel {
+                        override val title = "Preference 3"
+                        override val summary = stateOf("Summary 3")
+                    }
+                })
+                Preference(remember {
+                    object : PreferenceModel {
+                        override val title = "Preference 4"
+                        override val summary = stateOf("Summary 4")
+                    }
+                })
+            }
         }
     }
 }
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/SpinnerPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/SpinnerPage.kt
index 03b72d3..ec2f436 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/SpinnerPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/ui/SpinnerPage.kt
@@ -49,9 +49,13 @@
             }
     }
 
+    override fun getTitle(arguments: Bundle?): String {
+        return TITLE
+    }
+
     @Composable
     override fun Page(arguments: Bundle?) {
-        RegularScaffold(title = TITLE) {
+        RegularScaffold(title = getTitle(arguments)) {
             val selectedIndex = rememberSaveable { mutableStateOf(0) }
             Spinner(
                 options = (1..3).map { "Option $it" },
diff --git a/packages/SettingsLib/Spa/settings.gradle b/packages/SettingsLib/Spa/settings.gradle
index 897fa67..cef79c1 100644
--- a/packages/SettingsLib/Spa/settings.gradle
+++ b/packages/SettingsLib/Spa/settings.gradle
@@ -26,6 +26,7 @@
     repositories {
         google()
         mavenCentral()
+        maven { url "https://jitpack.io"}
     }
 }
 rootProject.name = "SpaLib"
diff --git a/packages/SettingsLib/Spa/spa/Android.bp b/packages/SettingsLib/Spa/spa/Android.bp
index 8b29366..037b45e 100644
--- a/packages/SettingsLib/Spa/spa/Android.bp
+++ b/packages/SettingsLib/Spa/spa/Android.bp
@@ -33,6 +33,7 @@
         "androidx.navigation_navigation-compose",
         "com.google.android.material_material",
         "lottie_compose",
+        "MPAndroidChart",
     ],
     kotlincflags: [
         "-Xjvm-default=all",
diff --git a/packages/SettingsLib/Spa/spa/build.gradle b/packages/SettingsLib/Spa/spa/build.gradle
index 2820ed7..7a20c747 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle
+++ b/packages/SettingsLib/Spa/spa/build.gradle
@@ -59,9 +59,10 @@
     api "androidx.compose.material:material-icons-extended:$jetpack_compose_version"
     api "androidx.compose.runtime:runtime-livedata:$jetpack_compose_version"
     api "androidx.compose.ui:ui-tooling-preview:$jetpack_compose_version"
-    api "androidx.lifecycle:lifecycle-livedata-ktx:2.6.0-alpha02"
-    api "androidx.navigation:navigation-compose:2.5.0"
-    api "com.google.android.material:material:1.6.1"
+    api "androidx.lifecycle:lifecycle-livedata-ktx:2.6.0-alpha03"
+    api "androidx.navigation:navigation-compose:2.6.0-alpha03"
+    api "com.github.PhilJay:MPAndroidChart:v3.1.0-alpha"
+    api "com.google.android.material:material:1.7.0-alpha03"
     debugApi "androidx.compose.ui:ui-tooling:$jetpack_compose_version"
     implementation "com.airbnb.android:lottie-compose:5.2.0"
 }
diff --git a/packages/SettingsLib/Spa/spa/res/values/themes.xml b/packages/SettingsLib/Spa/spa/res/values/themes.xml
index e0e5fc2..25846ec 100644
--- a/packages/SettingsLib/Spa/spa/res/values/themes.xml
+++ b/packages/SettingsLib/Spa/spa/res/values/themes.xml
@@ -16,12 +16,10 @@
 -->
 <resources>
 
-    <style name="Theme.SpaLib" parent="Theme.Material3.DayNight.NoActionBar">
+    <style name="Theme.SpaLib" parent="@android:style/Theme.DeviceDefault.Settings">
         <item name="android:statusBarColor">@android:color/transparent</item>
         <item name="android:navigationBarColor">@android:color/transparent</item>
-    </style>
-
-    <style name="Theme.SpaLib.DayNight">
-        <item name="android:windowLightStatusBar">true</item>
+        <item name="android:windowActionBar">false</item>
+        <item name="android:windowNoTitle">true</item>
     </style>
 </resources>
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
index d3efaa7..c3c90ab 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/BrowseActivity.kt
@@ -27,6 +27,7 @@
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.ui.platform.LocalLifecycleOwner
+import androidx.core.view.WindowCompat
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.LifecycleEventObserver
 import androidx.navigation.NavGraph.Companion.findStartDestination
@@ -66,8 +67,9 @@
     private val spaEnvironment get() = SpaEnvironmentFactory.instance
 
     override fun onCreate(savedInstanceState: Bundle?) {
-        setTheme(R.style.Theme_SpaLib_DayNight)
+        setTheme(R.style.Theme_SpaLib)
         super.onCreate(savedInstanceState)
+        WindowCompat.setDecorFitsSystemWindows(window, false)
         spaEnvironment.logger.message(TAG, "onCreate", category = LogCategory.FRAMEWORK)
 
         setContent {
@@ -83,35 +85,19 @@
         val navController = rememberNavController()
         val nullPage = SettingsPage.createNull()
         CompositionLocalProvider(navController.localNavController()) {
-            NavHost(navController, nullPage.sppName) {
+            NavHost(
+                navController = navController,
+                startDestination = nullPage.sppName,
+            ) {
                 composable(nullPage.sppName) {}
                 for (spp in sppRepository.getAllProviders()) {
                     composable(
                         route = spp.name + spp.parameter.navRoute(),
                         arguments = spp.parameter,
                     ) { navBackStackEntry ->
-                        val lifecycleOwner = LocalLifecycleOwner.current
-                        val sp = remember(navBackStackEntry.arguments) {
+                        PageLogger(remember(navBackStackEntry.arguments) {
                             spp.createSettingsPage(arguments = navBackStackEntry.arguments)
-                        }
-
-                        DisposableEffect(lifecycleOwner) {
-                            val observer = LifecycleEventObserver { _, event ->
-                                if (event == Lifecycle.Event.ON_START) {
-                                    sp.enterPage()
-                                } else if (event == Lifecycle.Event.ON_STOP) {
-                                    sp.leavePage()
-                                }
-                            }
-
-                            // Add the observer to the lifecycle
-                            lifecycleOwner.lifecycle.addObserver(observer)
-
-                            // When the effect leaves the Composition, remove the observer
-                            onDispose {
-                                lifecycleOwner.lifecycle.removeObserver(observer)
-                            }
-                        }
+                        })
 
                         spp.Page(navBackStackEntry.arguments)
                     }
@@ -122,6 +108,28 @@
     }
 
     @Composable
+    private fun PageLogger(settingsPage: SettingsPage) {
+        val lifecycleOwner = LocalLifecycleOwner.current
+        DisposableEffect(lifecycleOwner) {
+            val observer = LifecycleEventObserver { _, event ->
+                if (event == Lifecycle.Event.ON_START) {
+                    settingsPage.enterPage()
+                } else if (event == Lifecycle.Event.ON_STOP) {
+                    settingsPage.leavePage()
+                }
+            }
+
+            // Add the observer to the lifecycle
+            lifecycleOwner.lifecycle.addObserver(observer)
+
+            // When the effect leaves the Composition, remove the observer
+            onDispose {
+                lifecycleOwner.lifecycle.removeObserver(observer)
+            }
+        }
+    }
+
+    @Composable
     private fun InitialDestinationNavigator() {
         val sppRepository by spaEnvironment.pageProviderRepository
         val destinationNavigated = rememberSaveable { mutableStateOf(false) }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt
index e63e4c9..14b1629 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsEntryRepository.kt
@@ -30,10 +30,14 @@
     val injectEntry: SettingsEntry,
 )
 
+private fun SettingsPage.getTitle(sppRepository: SettingsPageProviderRepository): String {
+    return sppRepository.getProviderOrNull(sppName)!!.getTitle(arguments)
+}
+
 /**
  * The repository to maintain all Settings entries
  */
-class SettingsEntryRepository(sppRepository: SettingsPageProviderRepository) {
+class SettingsEntryRepository(private val sppRepository: SettingsPageProviderRepository) {
     // Map of entry unique Id to entry
     private val entryMap: Map<String, SettingsEntry>
 
@@ -118,8 +122,9 @@
         return entryPath.map {
             if (it.toPage == null)
                 defaultTitle
-            else
-                it.toPage.getTitle()!!
+            else {
+                it.toPage.getTitle(sppRepository)
+            }
         }
     }
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
index 2fa9229..bb287d1 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPage.kt
@@ -95,11 +95,6 @@
         return false
     }
 
-    fun getTitle(): String? {
-        val sppRepository by SpaEnvironmentFactory.instance.pageProviderRepository
-        return sppRepository.getProviderOrNull(sppName)?.getTitle(arguments)
-    }
-
     fun enterPage() {
         SpaEnvironmentFactory.instance.logger.event(
             id,
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
index f8963b2..151b50cd 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SettingsPageProvider.kt
@@ -19,6 +19,7 @@
 import android.os.Bundle
 import androidx.compose.runtime.Composable
 import androidx.navigation.NamedNavArgument
+import com.android.settingslib.spa.widget.scaffold.RegularScaffold
 
 /**
  * An SettingsPageProvider which is used to create Settings page instances.
@@ -36,13 +37,19 @@
     val parameter: List<NamedNavArgument>
         get() = emptyList()
 
-    /** The [Composable] used to render this page. */
-    @Composable
-    fun Page(arguments: Bundle?)
+    fun getTitle(arguments: Bundle?): String = displayName ?: name
 
     fun buildEntry(arguments: Bundle?): List<SettingsEntry> = emptyList()
 
-    fun getTitle(arguments: Bundle?): String = displayName ?: name
+    /** The [Composable] used to render this page. */
+    @Composable
+    fun Page(arguments: Bundle?) {
+        RegularScaffold(title = getTitle(arguments)) {
+            for (entry in buildEntry(arguments)) {
+                entry.UiLayout()
+            }
+        }
+    }
 }
 
 fun SettingsPageProvider.createSettingsPage(arguments: Bundle? = null): SettingsPage {
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
index 5baee4f..b831043 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/common/SpaEnvironment.kt
@@ -17,7 +17,10 @@
 package com.android.settingslib.spa.framework.common
 
 import android.app.Activity
+import android.content.Context
 import android.util.Log
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.platform.LocalContext
 
 private const val TAG = "SpaEnvironment"
 
@@ -29,6 +32,20 @@
         Log.d(TAG, "reset")
     }
 
+    @Composable
+    fun resetForPreview() {
+        val context = LocalContext.current
+        spaEnvironment = object : SpaEnvironment(context) {
+            override val pageProviderRepository = lazy {
+                SettingsPageProviderRepository(
+                    allPageProviders = emptyList(),
+                    rootPages = emptyList()
+                )
+            }
+        }
+        Log.d(TAG, "resetForPreview")
+    }
+
     val instance: SpaEnvironment
         get() {
             if (spaEnvironment == null)
@@ -37,11 +54,14 @@
         }
 }
 
-abstract class SpaEnvironment {
+abstract class SpaEnvironment(context: Context) {
     abstract val pageProviderRepository: Lazy<SettingsPageProviderRepository>
 
     val entryRepository = lazy { SettingsEntryRepository(pageProviderRepository.value) }
 
+    // In Robolectric test, applicationContext is not available. Use context as fallback.
+    val appContext: Context = context.applicationContext ?: context
+
     open val browseActivityClass: Class<out Activity>? = null
 
     open val entryProviderAuthorities: String? = null
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/PaddingValuesExt.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/PaddingValuesExt.kt
new file mode 100644
index 0000000..18335ff
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/compose/PaddingValuesExt.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.framework.compose
+
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+
+internal fun PaddingValues.horizontalValues(): PaddingValues = HorizontalPaddingValues(this)
+
+internal fun PaddingValues.verticalValues(): PaddingValues = VerticalPaddingValues(this)
+
+private class HorizontalPaddingValues(private val paddingValues: PaddingValues) : PaddingValues {
+    override fun calculateLeftPadding(layoutDirection: LayoutDirection) =
+        paddingValues.calculateLeftPadding(layoutDirection)
+
+    override fun calculateTopPadding(): Dp = 0.dp
+
+    override fun calculateRightPadding(layoutDirection: LayoutDirection) =
+        paddingValues.calculateRightPadding(layoutDirection)
+
+    override fun calculateBottomPadding() = 0.dp
+}
+
+private class VerticalPaddingValues(private val paddingValues: PaddingValues) : PaddingValues {
+    override fun calculateLeftPadding(layoutDirection: LayoutDirection) = 0.dp
+
+    override fun calculateTopPadding(): Dp = paddingValues.calculateTopPadding()
+
+    override fun calculateRightPadding(layoutDirection: LayoutDirection) = 0.dp
+
+    override fun calculateBottomPadding() = paddingValues.calculateBottomPadding()
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugActivity.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugActivity.kt
index 9eaa88a..26491d5 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugActivity.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/debug/DebugActivity.kt
@@ -62,7 +62,7 @@
     private val spaEnvironment get() = SpaEnvironmentFactory.instance
 
     override fun onCreate(savedInstanceState: Bundle?) {
-        setTheme(R.style.Theme_SpaLib_DayNight)
+        setTheme(R.style.Theme_SpaLib)
         super.onCreate(savedInstanceState)
         spaEnvironment.logger.message(TAG, "onCreate", category = LogCategory.FRAMEWORK)
 
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsFontFamily.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsFontFamily.kt
index 8e8805a..9479228 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsFontFamily.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsFontFamily.kt
@@ -21,45 +21,51 @@
 import android.annotation.SuppressLint
 import android.content.Context
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.remember
-import androidx.compose.ui.platform.LocalContext
-import androidx.compose.ui.platform.LocalInspectionMode
 import androidx.compose.ui.text.ExperimentalTextApi
 import androidx.compose.ui.text.font.DeviceFontFamilyName
 import androidx.compose.ui.text.font.Font
 import androidx.compose.ui.text.font.FontFamily
 import androidx.compose.ui.text.font.FontWeight
+import com.android.settingslib.spa.framework.compose.rememberContext
 
 internal data class SettingsFontFamily(
-    val brand: FontFamily = FontFamily.Default,
-    val plain: FontFamily = FontFamily.Default,
+    val brand: FontFamily,
+    val plain: FontFamily,
 )
 
-private fun Context.getSettingsFontFamily(inInspection: Boolean): SettingsFontFamily {
-    if (inInspection) {
-        return SettingsFontFamily()
-    }
+private fun Context.getSettingsFontFamily(): SettingsFontFamily {
     return SettingsFontFamily(
-        brand = FontFamily(
-            Font(getFontFamilyName("config_headlineFontFamily"), FontWeight.Normal),
-            Font(getFontFamilyName("config_headlineFontFamilyMedium"), FontWeight.Medium),
+        brand = getFontFamily(
+            configFontFamilyNormal = "config_headlineFontFamily",
+            configFontFamilyMedium = "config_headlineFontFamilyMedium",
         ),
-        plain = FontFamily(
-            Font(getFontFamilyName("config_bodyFontFamily"), FontWeight.Normal),
-            Font(getFontFamilyName("config_bodyFontFamilyMedium"), FontWeight.Medium),
+        plain = getFontFamily(
+            configFontFamilyNormal = "config_bodyFontFamily",
+            configFontFamilyMedium = "config_bodyFontFamilyMedium",
         ),
     )
 }
 
-private fun Context.getFontFamilyName(configName: String): DeviceFontFamilyName {
+private fun Context.getFontFamily(
+    configFontFamilyNormal: String,
+    configFontFamilyMedium: String,
+): FontFamily {
+    val fontFamilyNormal = getAndroidConfig(configFontFamilyNormal)
+    val fontFamilyMedium = getAndroidConfig(configFontFamilyMedium)
+    if (fontFamilyNormal.isEmpty() || fontFamilyMedium.isEmpty()) return FontFamily.Default
+    return FontFamily(
+        Font(DeviceFontFamilyName(fontFamilyNormal), FontWeight.Normal),
+        Font(DeviceFontFamilyName(fontFamilyMedium), FontWeight.Medium),
+    )
+}
+
+private fun Context.getAndroidConfig(configName: String): String {
     @SuppressLint("DiscouragedApi")
     val configId = resources.getIdentifier(configName, "string", "android")
-    return DeviceFontFamilyName(resources.getString(configId))
+    return resources.getString(configId)
 }
 
 @Composable
 internal fun rememberSettingsFontFamily(): SettingsFontFamily {
-    val context = LocalContext.current
-    val inInspection = LocalInspectionMode.current
-    return remember { context.getSettingsFontFamily(inInspection) }
+    return rememberContext(Context::getSettingsFontFamily)
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/BarChart.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/BarChart.kt
new file mode 100644
index 0000000..0b0f07e
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/BarChart.kt
@@ -0,0 +1,202 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.widget.chart
+
+import android.view.ViewGroup
+import android.widget.LinearLayout
+import androidx.compose.animation.Crossfade
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.material3.ColorScheme
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.viewinterop.AndroidView
+import com.android.settingslib.spa.framework.theme.divider
+import com.github.mikephil.charting.charts.BarChart
+import com.github.mikephil.charting.components.XAxis
+import com.github.mikephil.charting.data.BarData
+import com.github.mikephil.charting.data.BarDataSet
+import com.github.mikephil.charting.data.BarEntry
+import com.github.mikephil.charting.formatter.IAxisValueFormatter
+
+/**
+ * The chart settings model for [BarChart].
+ */
+interface BarChartModel {
+    /**
+     * The chart data list for [BarChart].
+     */
+    val chartDataList: List<BarChartData>
+
+    /**
+     * The label text formatter for x value.
+     */
+    val xValueFormatter: IAxisValueFormatter?
+        get() = null
+
+    /**
+     * The label text formatter for y value.
+     */
+    val yValueFormatter: IAxisValueFormatter?
+        get() = null
+
+    /**
+     * The minimum value for y-axis.
+     */
+    val yAxisMinValue: Float
+        get() = 0f
+
+    /**
+     * The maximum value for y-axis.
+     */
+    val yAxisMaxValue: Float
+        get() = 1f
+
+    /**
+     * The label count for y-axis.
+     */
+    val yAxisLabelCount: Int
+        get() = 3
+}
+
+data class BarChartData(
+    var x: Float?,
+    var y: Float?,
+)
+
+@Composable
+fun BarChart(barChartModel: BarChartModel) {
+    BarChart(
+        chartDataList = barChartModel.chartDataList,
+        xValueFormatter = barChartModel.xValueFormatter,
+        yValueFormatter = barChartModel.yValueFormatter,
+        yAxisMinValue = barChartModel.yAxisMinValue,
+        yAxisMaxValue = barChartModel.yAxisMaxValue,
+        yAxisLabelCount = barChartModel.yAxisLabelCount,
+    )
+}
+
+@Composable
+fun BarChart(
+    chartDataList: List<BarChartData>,
+    modifier: Modifier = Modifier,
+    xValueFormatter: IAxisValueFormatter? = null,
+    yValueFormatter: IAxisValueFormatter? = null,
+    yAxisMinValue: Float = 0f,
+    yAxisMaxValue: Float = 30f,
+    yAxisLabelCount: Int = 4,
+) {
+    Column(
+        modifier = modifier
+            .fillMaxWidth()
+            .wrapContentHeight(),
+        horizontalAlignment = Alignment.CenterHorizontally,
+        verticalArrangement = Arrangement.Center
+    ) {
+        Column(
+            modifier = Modifier
+                .padding(16.dp)
+                .height(170.dp),
+            horizontalAlignment = Alignment.CenterHorizontally,
+            verticalArrangement = Arrangement.Center
+        ) {
+            val colorScheme = MaterialTheme.colorScheme
+            val labelTextColor = colorScheme.onSurfaceVariant.toArgb()
+            val labelTextSize = MaterialTheme.typography.bodyMedium.fontSize.value
+            Crossfade(targetState = chartDataList) { barChartData ->
+                AndroidView(factory = { context ->
+                    BarChart(context).apply {
+                        // Fixed Settings.
+                        layoutParams = LinearLayout.LayoutParams(
+                            ViewGroup.LayoutParams.MATCH_PARENT,
+                            ViewGroup.LayoutParams.MATCH_PARENT,
+                        )
+                        this.description.isEnabled = false
+                        this.legend.isEnabled = false
+                        this.extraBottomOffset = 4f
+                        this.setScaleEnabled(false)
+
+                        this.xAxis.position = XAxis.XAxisPosition.BOTTOM
+                        this.xAxis.setDrawGridLines(false)
+                        this.xAxis.setDrawAxisLine(false)
+                        this.xAxis.textColor = labelTextColor
+                        this.xAxis.textSize = labelTextSize
+                        this.xAxis.yOffset = 10f
+
+                        this.axisLeft.isEnabled = false
+                        this.axisRight.setDrawAxisLine(false)
+                        this.axisRight.textSize = labelTextSize
+                        this.axisRight.textColor = labelTextColor
+                        this.axisRight.gridColor = colorScheme.divider.toArgb()
+                        this.axisRight.xOffset = 10f
+
+                        // Customizable Settings.
+                        this.xAxis.valueFormatter = xValueFormatter
+                        this.axisRight.valueFormatter = yValueFormatter
+
+                        this.axisLeft.axisMinimum = yAxisMinValue
+                        this.axisLeft.axisMaximum = yAxisMaxValue
+                        this.axisRight.axisMinimum = yAxisMinValue
+                        this.axisRight.axisMaximum = yAxisMaxValue
+
+                        this.axisRight.setLabelCount(yAxisLabelCount, true)
+                    }
+                },
+                    modifier = Modifier
+                        .wrapContentSize()
+                        .padding(4.dp),
+                    update = {
+                        updateBarChartWithData(it, barChartData, colorScheme)
+                    }
+                )
+            }
+        }
+    }
+}
+
+fun updateBarChartWithData(
+    chart: BarChart,
+    data: List<BarChartData>,
+    colorScheme: ColorScheme
+) {
+    val entries = ArrayList<BarEntry>()
+    for (i in data.indices) {
+        val item = data[i]
+        entries.add(BarEntry(item.x ?: 0.toFloat(), item.y ?: 0.toFloat()))
+    }
+
+    val ds = BarDataSet(entries, "")
+    ds.colors = arrayListOf(colorScheme.surfaceVariant.toArgb())
+    ds.setDrawValues(false)
+    ds.isHighlightEnabled = true
+    ds.highLightColor = colorScheme.primary.toArgb()
+    ds.highLightAlpha = 255
+    // TODO: Sets round corners for bars.
+
+    val d = BarData(ds)
+    chart.data = d
+    chart.invalidate()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotifPanelEventsModule.java b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/ColorPalette.kt
similarity index 60%
copy from packages/SystemUI/src/com/android/systemui/shade/NotifPanelEventsModule.java
copy to packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/ColorPalette.kt
index 6772384..70bc017 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotifPanelEventsModule.java
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/ColorPalette.kt
@@ -14,17 +14,16 @@
  * limitations under the License.
  */
 
-package com.android.systemui.shade;
+package com.android.settingslib.spa.widget.chart
 
-import com.android.systemui.dagger.SysUISingleton;
+import androidx.compose.ui.graphics.Color
 
-import dagger.Binds;
-import dagger.Module;
-
-/** Provides a {@link NotifPanelEvents} in {@link SysUISingleton} scope. */
-@Module
-public abstract class NotifPanelEventsModule {
-    @Binds
-    abstract NotifPanelEvents bindPanelEvents(
-            NotificationPanelViewController.PanelEventsEmitter impl);
+object ColorPalette {
+    // Alpha = 1
+    val red: Color = Color(0xffd93025)
+    val orange: Color = Color(0xffe8710a)
+    val yellow: Color = Color(0xfff9ab00)
+    val green: Color = Color(0xff1e8e3e)
+    val cyan: Color = Color(0xff12b5cb)
+    val blue: Color = Color(0xff1a73e8)
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/LineChart.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/LineChart.kt
new file mode 100644
index 0000000..7d48265
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/LineChart.kt
@@ -0,0 +1,217 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.widget.chart
+
+import android.view.ViewGroup
+import android.widget.LinearLayout
+import androidx.compose.animation.Crossfade
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.material3.ColorScheme
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.viewinterop.AndroidView
+import com.android.settingslib.spa.framework.theme.divider
+import com.github.mikephil.charting.charts.LineChart
+import com.github.mikephil.charting.components.XAxis
+import com.github.mikephil.charting.data.Entry
+import com.github.mikephil.charting.data.LineData
+import com.github.mikephil.charting.data.LineDataSet
+import com.github.mikephil.charting.formatter.IAxisValueFormatter
+
+/**
+ * The chart settings model for [LineChart].
+ */
+interface LineChartModel {
+    /**
+     * The chart data list for [LineChart].
+     */
+    val chartDataList: List<LineChartData>
+
+    /**
+     * The label text formatter for x value.
+     */
+    val xValueFormatter: IAxisValueFormatter?
+        get() = null
+
+    /**
+     * The label text formatter for y value.
+     */
+    val yValueFormatter: IAxisValueFormatter?
+        get() = null
+
+    /**
+     * The minimum value for y-axis.
+     */
+    val yAxisMinValue: Float
+        get() = 0f
+
+    /**
+     * The maximum value for y-axis.
+     */
+    val yAxisMaxValue: Float
+        get() = 1f
+
+    /**
+     * The label count for y-axis.
+     */
+    val yAxisLabelCount: Int
+        get() = 3
+
+    /**
+     * Indicates whether to smooth the line.
+     */
+    val showSmoothLine: Boolean
+        get() = true
+}
+
+data class LineChartData(
+    var x: Float?,
+    var y: Float?,
+)
+
+@Composable
+fun LineChart(lineChartModel: LineChartModel) {
+    LineChart(
+        chartDataList = lineChartModel.chartDataList,
+        xValueFormatter = lineChartModel.xValueFormatter,
+        yValueFormatter = lineChartModel.yValueFormatter,
+        yAxisMinValue = lineChartModel.yAxisMinValue,
+        yAxisMaxValue = lineChartModel.yAxisMaxValue,
+        yAxisLabelCount = lineChartModel.yAxisLabelCount,
+        showSmoothLine = lineChartModel.showSmoothLine,
+    )
+}
+
+@Composable
+fun LineChart(
+    chartDataList: List<LineChartData>,
+    modifier: Modifier = Modifier,
+    xValueFormatter: IAxisValueFormatter? = null,
+    yValueFormatter: IAxisValueFormatter? = null,
+    yAxisMinValue: Float = 0f,
+    yAxisMaxValue: Float = 1f,
+    yAxisLabelCount: Int = 3,
+    showSmoothLine: Boolean = true,
+) {
+    Column(
+        modifier = modifier
+            .fillMaxWidth()
+            .wrapContentHeight(),
+        horizontalAlignment = Alignment.CenterHorizontally,
+        verticalArrangement = Arrangement.Center
+    ) {
+        Column(
+            modifier = Modifier
+                .padding(16.dp)
+                .height(170.dp),
+            horizontalAlignment = Alignment.CenterHorizontally,
+            verticalArrangement = Arrangement.Center
+        ) {
+            val colorScheme = MaterialTheme.colorScheme
+            val labelTextColor = colorScheme.onSurfaceVariant.toArgb()
+            val labelTextSize = MaterialTheme.typography.bodyMedium.fontSize.value
+            Crossfade(targetState = chartDataList) { lineChartData ->
+                AndroidView(factory = { context ->
+                    LineChart(context).apply {
+                        // Fixed Settings.
+                        layoutParams = LinearLayout.LayoutParams(
+                            ViewGroup.LayoutParams.MATCH_PARENT,
+                            ViewGroup.LayoutParams.MATCH_PARENT,
+                        )
+                        this.description.isEnabled = false
+                        this.legend.isEnabled = false
+                        this.extraBottomOffset = 4f
+                        this.setTouchEnabled(false)
+
+                        this.xAxis.position = XAxis.XAxisPosition.BOTTOM
+                        this.xAxis.setDrawGridLines(false)
+                        this.xAxis.setDrawAxisLine(false)
+                        this.xAxis.textColor = labelTextColor
+                        this.xAxis.textSize = labelTextSize
+                        this.xAxis.yOffset = 10f
+
+                        this.axisLeft.isEnabled = false
+
+                        this.axisRight.setDrawAxisLine(false)
+                        this.axisRight.textSize = labelTextSize
+                        this.axisRight.textColor = labelTextColor
+                        this.axisRight.gridColor = colorScheme.divider.toArgb()
+                        this.axisRight.xOffset = 10f
+                        this.axisRight.isGranularityEnabled = true
+
+                        // Customizable Settings.
+                        this.xAxis.valueFormatter = xValueFormatter
+                        this.axisRight.valueFormatter = yValueFormatter
+
+                        this.axisLeft.axisMinimum =
+                            yAxisMinValue - 0.01f * (yAxisMaxValue - yAxisMinValue)
+                        this.axisRight.axisMinimum =
+                            yAxisMinValue - 0.01f * (yAxisMaxValue - yAxisMinValue)
+                        this.axisRight.granularity =
+                            (yAxisMaxValue - yAxisMinValue) / (yAxisLabelCount - 1)
+                    }
+                },
+                    modifier = Modifier
+                        .wrapContentSize()
+                        .padding(4.dp),
+                    update = {
+                        updateLineChartWithData(it, lineChartData, colorScheme, showSmoothLine)
+                    })
+            }
+        }
+    }
+}
+
+fun updateLineChartWithData(
+    chart: LineChart,
+    data: List<LineChartData>,
+    colorScheme: ColorScheme,
+    showSmoothLine: Boolean
+) {
+    val entries = ArrayList<Entry>()
+    for (i in data.indices) {
+        val item = data[i]
+        entries.add(Entry(item.x ?: 0.toFloat(), item.y ?: 0.toFloat()))
+    }
+
+    val ds = LineDataSet(entries, "")
+    ds.colors = arrayListOf(colorScheme.primary.toArgb())
+    ds.lineWidth = 2f
+    if (showSmoothLine) {
+        ds.mode = LineDataSet.Mode.CUBIC_BEZIER
+    }
+    ds.setDrawValues(false)
+    ds.setDrawCircles(false)
+    ds.setDrawFilled(true)
+    ds.fillColor = colorScheme.primary.toArgb()
+    ds.fillAlpha = 38
+    // TODO: enable gradient fill color for line chart.
+
+    val d = LineData(ds)
+    chart.data = d
+    chart.invalidate()
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/PieChart.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/PieChart.kt
new file mode 100644
index 0000000..51a8d0d
--- /dev/null
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/PieChart.kt
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.widget.chart
+
+import android.view.ViewGroup
+import android.widget.LinearLayout
+import androidx.compose.animation.Crossfade
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.toArgb
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.viewinterop.AndroidView
+import com.github.mikephil.charting.charts.PieChart
+import com.github.mikephil.charting.data.PieData
+import com.github.mikephil.charting.data.PieDataSet
+import com.github.mikephil.charting.data.PieEntry
+
+/**
+ * The chart settings model for [PieChart].
+ */
+interface PieChartModel {
+    /**
+     * The chart data list for [PieChart].
+     */
+    val chartDataList: List<PieChartData>
+
+    /**
+     * The center text in the hole of [PieChart].
+     */
+    val centerText: String?
+        get() = null
+}
+
+val colorPalette = arrayListOf(
+    ColorPalette.blue.toArgb(),
+    ColorPalette.red.toArgb(),
+    ColorPalette.yellow.toArgb(),
+    ColorPalette.green.toArgb(),
+    ColorPalette.orange.toArgb(),
+    ColorPalette.cyan.toArgb(),
+    Color.Blue.toArgb()
+)
+
+data class PieChartData(
+    var value: Float?,
+    var label: String?,
+)
+
+@Composable
+fun PieChart(pieChartModel: PieChartModel) {
+    PieChart(
+        chartDataList = pieChartModel.chartDataList,
+        centerText = pieChartModel.centerText,
+    )
+}
+
+@Composable
+fun PieChart(
+    chartDataList: List<PieChartData>,
+    modifier: Modifier = Modifier,
+    centerText: String? = null,
+) {
+    Column(
+        modifier = modifier
+            .fillMaxWidth()
+            .wrapContentHeight(),
+        horizontalAlignment = Alignment.CenterHorizontally,
+        verticalArrangement = Arrangement.Center
+    ) {
+        Column(
+            modifier = Modifier
+                .padding(16.dp)
+                .height(280.dp),
+            horizontalAlignment = Alignment.CenterHorizontally,
+            verticalArrangement = Arrangement.Center
+        ) {
+            val colorScheme = MaterialTheme.colorScheme
+            val labelTextColor = colorScheme.onSurfaceVariant.toArgb()
+            val labelTextSize = MaterialTheme.typography.bodyMedium.fontSize.value
+            val centerTextSize = MaterialTheme.typography.titleLarge.fontSize.value
+            Crossfade(targetState = chartDataList) { pieChartData ->
+                AndroidView(factory = { context ->
+                    PieChart(context).apply {
+                        // Fixed settings.`
+                        layoutParams = LinearLayout.LayoutParams(
+                            ViewGroup.LayoutParams.MATCH_PARENT,
+                            ViewGroup.LayoutParams.MATCH_PARENT,
+                        )
+                        this.isRotationEnabled = false
+                        this.description.isEnabled = false
+                        this.legend.isEnabled = false
+                        this.setTouchEnabled(false)
+
+                        this.isDrawHoleEnabled = true
+                        this.holeRadius = 90.0f
+                        this.setHoleColor(Color.Transparent.toArgb())
+                        this.setEntryLabelColor(labelTextColor)
+                        this.setEntryLabelTextSize(labelTextSize)
+                        this.setCenterTextSize(centerTextSize)
+                        this.setCenterTextColor(colorScheme.onSurface.toArgb())
+
+                        // Customizable settings.
+                        this.centerText = centerText
+                    }
+                },
+                    modifier = Modifier
+                        .wrapContentSize()
+                        .padding(4.dp), update = {
+                        updatePieChartWithData(it, pieChartData)
+                    })
+            }
+        }
+    }
+}
+
+fun updatePieChartWithData(
+    chart: PieChart,
+    data: List<PieChartData>,
+) {
+    val entries = ArrayList<PieEntry>()
+    for (i in data.indices) {
+        val item = data[i]
+        entries.add(PieEntry(item.value ?: 0.toFloat(), item.label ?: ""))
+    }
+
+    val ds = PieDataSet(entries, "")
+    ds.setDrawValues(false)
+    ds.colors = colorPalette
+    ds.sliceSpace = 2f
+    ds.yValuePosition = PieDataSet.ValuePosition.OUTSIDE_SLICE
+    ds.xValuePosition = PieDataSet.ValuePosition.OUTSIDE_SLICE
+    ds.valueLineColor = Color.Transparent.toArgb()
+    ds.valueLinePart1Length = 0.1f
+    ds.valueLinePart2Length = 0f
+
+    val d = PieData(ds)
+    chart.data = d
+    chart.invalidate()
+}
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/HomeScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/HomeScaffold.kt
index eb20ac5..711c8a7 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/HomeScaffold.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/HomeScaffold.kt
@@ -20,6 +20,7 @@
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.systemBarsPadding
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.material3.MaterialTheme
@@ -34,6 +35,7 @@
         Modifier
             .fillMaxSize()
             .background(color = MaterialTheme.colorScheme.background)
+            .systemBarsPadding()
             .verticalScroll(rememberScrollState()),
     ) {
         Text(
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/RegularScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/RegularScaffold.kt
index 9a17b2a..d17a8dc 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/RegularScaffold.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/RegularScaffold.kt
@@ -19,7 +19,7 @@
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.RowScope
 import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.verticalScroll
 import androidx.compose.material3.Scaffold
@@ -27,6 +27,8 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.tooling.preview.Preview
 import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
 
 /**
  * A [Scaffold] which content is scrollable and wrapped in a [Column].
@@ -42,8 +44,9 @@
 ) {
     SettingsScaffold(title, actions) { paddingValues ->
         Column(Modifier.verticalScroll(rememberScrollState())) {
-            Spacer(Modifier.padding(paddingValues))
+            Spacer(Modifier.height(paddingValues.calculateTopPadding()))
             content()
+            Spacer(Modifier.height(paddingValues.calculateBottomPadding()))
         }
     }
 }
@@ -52,6 +55,13 @@
 @Composable
 private fun RegularScaffoldPreview() {
     SettingsTheme {
-        RegularScaffold(title = "Display") {}
+        RegularScaffold(title = "Display") {
+            Preference(object : PreferenceModel {
+                override val title = "Item 1"
+            })
+            Preference(object : PreferenceModel {
+                override val title = "Item 2"
+            })
+        }
     }
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt
index 4f83ad6..efc623a 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SearchScaffold.kt
@@ -14,13 +14,12 @@
  * limitations under the License.
  */
 
-@file:OptIn(ExperimentalMaterial3Api::class)
-
 package com.android.settingslib.spa.widget.scaffold
 
 import androidx.activity.compose.BackHandler
 import androidx.appcompat.R
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.RowScope
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
@@ -31,10 +30,13 @@
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Surface
 import androidx.compose.material3.Text
 import androidx.compose.material3.TextField
 import androidx.compose.material3.TextFieldDefaults
 import androidx.compose.material3.TopAppBar
+import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.material3.TopAppBarScrollBehavior
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.State
@@ -48,45 +50,57 @@
 import androidx.compose.ui.focus.FocusRequester
 import androidx.compose.ui.focus.focusRequester
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.input.nestedscroll.nestedScroll
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.text.input.ImeAction
 import androidx.compose.ui.text.input.TextFieldValue
 import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.Dp
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.viewmodel.compose.viewModel
 import com.android.settingslib.spa.framework.compose.hideKeyboardAction
+import com.android.settingslib.spa.framework.compose.horizontalValues
 import com.android.settingslib.spa.framework.theme.SettingsOpacity
 import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
 
 /**
  * A [Scaffold] which content is can be full screen, and with a search feature built-in.
  */
+@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 fun SearchScaffold(
     title: String,
     actions: @Composable RowScope.() -> Unit = {},
-    content: @Composable (searchQuery: State<String>) -> Unit,
+    content: @Composable (bottomPadding: Dp, searchQuery: State<String>) -> Unit,
 ) {
     val viewModel: SearchScaffoldViewModel = viewModel()
 
+    val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
     Scaffold(
+        modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
         topBar = {
             SearchableTopAppBar(
                 title = title,
                 actions = actions,
+                scrollBehavior = scrollBehavior,
                 searchQuery = viewModel.searchQuery,
             ) { viewModel.searchQuery = it }
         },
     ) { paddingValues ->
         Box(
             Modifier
-                .padding(paddingValues)
-                .fillMaxSize()
+                .padding(paddingValues.horizontalValues())
+                .padding(top = paddingValues.calculateTopPadding())
+                .fillMaxSize(),
         ) {
-            val searchQuery = remember {
-                derivedStateOf { viewModel.searchQuery?.text ?: "" }
-            }
-            content(searchQuery)
+            content(
+                bottomPadding = paddingValues.calculateBottomPadding(),
+                searchQuery = remember {
+                    derivedStateOf { viewModel.searchQuery?.text ?: "" }
+                },
+            )
         }
     }
 }
@@ -95,10 +109,12 @@
     var searchQuery: TextFieldValue? by mutableStateOf(null)
 }
 
+@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 private fun SearchableTopAppBar(
     title: String,
     actions: @Composable RowScope.() -> Unit,
+    scrollBehavior: TopAppBarScrollBehavior,
     searchQuery: TextFieldValue?,
     onSearchQueryChange: (TextFieldValue?) -> Unit,
 ) {
@@ -110,13 +126,17 @@
             actions = actions,
         )
     } else {
-        SettingsTopAppBar(title) {
-            SearchAction { onSearchQueryChange(TextFieldValue()) }
+        SettingsTopAppBar(title, scrollBehavior) {
+            SearchAction {
+                scrollBehavior.collapse()
+                onSearchQueryChange(TextFieldValue())
+            }
             actions()
         }
     }
 }
 
+@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 private fun SearchTopAppBar(
     query: TextFieldValue,
@@ -124,21 +144,24 @@
     onClose: () -> Unit,
     actions: @Composable RowScope.() -> Unit = {},
 ) {
-    TopAppBar(
-        title = { SearchBox(query, onQueryChange) },
-        modifier = Modifier.statusBarsPadding(),
-        navigationIcon = { CollapseAction(onClose) },
-        actions = {
-            if (query.text.isNotEmpty()) {
-                ClearAction { onQueryChange(TextFieldValue()) }
-            }
-            actions()
-        },
-        colors = settingsTopAppBarColors(),
-    )
+    Surface(color = SettingsTheme.colorScheme.surfaceHeader) {
+        TopAppBar(
+            title = { SearchBox(query, onQueryChange) },
+            modifier = Modifier.statusBarsPadding(),
+            navigationIcon = { CollapseAction(onClose) },
+            actions = {
+                if (query.text.isNotEmpty()) {
+                    ClearAction { onQueryChange(TextFieldValue()) }
+                }
+                actions()
+            },
+            colors = TopAppBarDefaults.smallTopAppBarColors(containerColor = Color.Transparent),
+        )
+    }
     BackHandler { onClose() }
 }
 
+@OptIn(ExperimentalMaterial3Api::class)
 @Composable
 private fun SearchBox(query: TextFieldValue, onQueryChange: (TextFieldValue) -> Unit) {
     val focusRequester = remember { FocusRequester() }
@@ -184,6 +207,15 @@
 @Composable
 private fun SearchScaffoldPreview() {
     SettingsTheme {
-        SearchScaffold(title = "App notifications") {}
+        SearchScaffold(title = "App notifications") { _, _ ->
+            Column {
+                Preference(object : PreferenceModel {
+                    override val title = "Item 1"
+                })
+                Preference(object : PreferenceModel {
+                    override val title = "Item 2"
+                })
+            }
+        }
     }
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt
index 3bc3dd7..f4e504a 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffold.kt
@@ -16,13 +16,23 @@
 
 package com.android.settingslib.spa.widget.scaffold
 
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.padding
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.Scaffold
+import androidx.compose.material3.TopAppBarDefaults
 import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.input.nestedscroll.nestedScroll
 import androidx.compose.ui.tooling.preview.Preview
+import com.android.settingslib.spa.framework.compose.horizontalValues
+import com.android.settingslib.spa.framework.compose.verticalValues
 import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
 
 /**
  * A [Scaffold] which content is can be full screen when needed.
@@ -34,16 +44,30 @@
     actions: @Composable RowScope.() -> Unit = {},
     content: @Composable (PaddingValues) -> Unit,
 ) {
+    val scrollBehavior = TopAppBarDefaults.exitUntilCollapsedScrollBehavior()
     Scaffold(
-        topBar = { SettingsTopAppBar(title, actions) },
-        content = content,
-    )
+        modifier = Modifier.nestedScroll(scrollBehavior.nestedScrollConnection),
+        topBar = { SettingsTopAppBar(title, scrollBehavior, actions) },
+    ) { paddingValues ->
+        Box(Modifier.padding(paddingValues.horizontalValues())) {
+            content(paddingValues.verticalValues())
+        }
+    }
 }
 
 @Preview
 @Composable
 private fun SettingsScaffoldPreview() {
     SettingsTheme {
-        SettingsScaffold(title = "Display") {}
+        SettingsScaffold(title = "Display") { paddingValues ->
+            Column(Modifier.padding(paddingValues)) {
+                Preference(object : PreferenceModel {
+                    override val title = "Item 1"
+                })
+                Preference(object : PreferenceModel {
+                    override val title = "Item 2"
+                })
+            }
+        }
     }
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTopAppBar.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTopAppBar.kt
index 9353520..f7cb035 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTopAppBar.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/scaffold/SettingsTopAppBar.kt
@@ -17,41 +17,70 @@
 package com.android.settingslib.spa.widget.scaffold
 
 import androidx.compose.foundation.layout.RowScope
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.asPaddingValues
+import androidx.compose.foundation.layout.navigationBars
 import androidx.compose.foundation.layout.padding
 import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.LargeTopAppBar
+import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
-import androidx.compose.material3.TopAppBar
 import androidx.compose.material3.TopAppBarDefaults
+import androidx.compose.material3.TopAppBarScrollBehavior
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.text.style.TextOverflow
+import com.android.settingslib.spa.framework.compose.horizontalValues
 import com.android.settingslib.spa.framework.theme.SettingsDimension
 import com.android.settingslib.spa.framework.theme.SettingsTheme
+import com.android.settingslib.spa.framework.theme.rememberSettingsTypography
 
 @OptIn(ExperimentalMaterial3Api::class)
 @Composable
 internal fun SettingsTopAppBar(
     title: String,
+    scrollBehavior: TopAppBarScrollBehavior,
     actions: @Composable RowScope.() -> Unit,
 ) {
-    TopAppBar(
-        title = {
-            Text(
-                text = title,
-                modifier = Modifier.padding(SettingsDimension.itemPaddingAround),
-                overflow = TextOverflow.Ellipsis,
-                maxLines = 1,
-            )
-        },
-        navigationIcon = { NavigateBack() },
-        actions = actions,
-        colors = settingsTopAppBarColors(),
+    val colorScheme = MaterialTheme.colorScheme
+    // TODO: Remove MaterialTheme() after top app bar color fixed in AndroidX.
+    MaterialTheme(
+        colorScheme = remember { colorScheme.copy(surface = colorScheme.background) },
+        typography = rememberSettingsTypography(),
+    ) {
+        LargeTopAppBar(
+            title = { Title(title) },
+            navigationIcon = { NavigateBack() },
+            actions = actions,
+            colors = largeTopAppBarColors(),
+            scrollBehavior = scrollBehavior,
+        )
+    }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+internal fun TopAppBarScrollBehavior.collapse() {
+    with(state) {
+        heightOffset = heightOffsetLimit
+    }
+}
+
+@Composable
+private fun Title(title: String) {
+    Text(
+        text = title,
+        modifier = Modifier
+            .padding(WindowInsets.navigationBars.asPaddingValues().horizontalValues())
+            .padding(SettingsDimension.itemPaddingAround),
+        overflow = TextOverflow.Ellipsis,
+        maxLines = 1,
     )
 }
 
 @OptIn(ExperimentalMaterial3Api::class)
 @Composable
-internal fun settingsTopAppBarColors() = TopAppBarDefaults.smallTopAppBarColors(
-    containerColor = SettingsTheme.colorScheme.surfaceHeader,
+private fun largeTopAppBarColors() = TopAppBarDefaults.largeTopAppBarColors(
+    containerColor = MaterialTheme.colorScheme.background,
     scrolledContainerColor = SettingsTheme.colorScheme.surfaceHeader,
 )
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/util/EntryHighlight.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/util/EntryHighlight.kt
index 652e54d..e26bdf7 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/util/EntryHighlight.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/util/EntryHighlight.kt
@@ -16,21 +16,43 @@
 
 package com.android.settingslib.spa.widget.util
 
+import androidx.compose.animation.animateColorAsState
+import androidx.compose.animation.core.RepeatMode
+import androidx.compose.animation.core.repeatable
+import androidx.compose.animation.core.tween
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Box
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
 import com.android.settingslib.spa.framework.common.LocalEntryDataProvider
+import com.android.settingslib.spa.framework.theme.SettingsTheme
 
 @Composable
 internal fun EntryHighlight(UiLayoutFn: @Composable () -> Unit) {
     val entryData = LocalEntryDataProvider.current
-    val isHighlighted = rememberSaveable { entryData.isHighlighted }
-    val backgroundColor =
-        if (isHighlighted) MaterialTheme.colorScheme.surfaceVariant else Color.Transparent
+    val entryIsHighlighted = rememberSaveable { entryData.isHighlighted }
+    var localHighlighted by rememberSaveable { mutableStateOf(false) }
+    SideEffect {
+        localHighlighted = entryIsHighlighted
+    }
+
+    val backgroundColor by animateColorAsState(
+        targetValue = when {
+            localHighlighted -> MaterialTheme.colorScheme.surfaceVariant
+            else -> SettingsTheme.colorScheme.background
+        },
+        animationSpec = repeatable(
+            iterations = 3,
+            animation = tween(durationMillis = 500),
+            repeatMode = RepeatMode.Restart
+        )
+    )
     Box(modifier = Modifier.background(color = backgroundColor)) {
         UiLayoutFn()
     }
diff --git a/packages/SettingsLib/Spa/tests/Android.bp b/packages/SettingsLib/Spa/tests/Android.bp
index 7491045..2449dec 100644
--- a/packages/SettingsLib/Spa/tests/Android.bp
+++ b/packages/SettingsLib/Spa/tests/Android.bp
@@ -31,6 +31,7 @@
         "androidx.compose.runtime_runtime",
         "androidx.compose.ui_ui-test-junit4",
         "androidx.compose.ui_ui-test-manifest",
+        "mockito-target-minus-junit4",
         "truth-prebuilt",
     ],
     kotlincflags: ["-Xjvm-default=all"],
diff --git a/packages/SettingsLib/Spa/tests/build.gradle b/packages/SettingsLib/Spa/tests/build.gradle
index 4b4c6a3..5261091 100644
--- a/packages/SettingsLib/Spa/tests/build.gradle
+++ b/packages/SettingsLib/Spa/tests/build.gradle
@@ -58,9 +58,10 @@
 }
 
 dependencies {
-    androidTestImplementation(project(":spa"))
-    androidTestImplementation 'androidx.test.ext:junit-ktx:1.1.3'
-    androidTestImplementation("androidx.compose.ui:ui-test-junit4:$jetpack_compose_version")
-    androidTestImplementation 'com.google.truth:truth:1.1.3'
+    androidTestImplementation project(":spa")
+    androidTestImplementation "androidx.test.ext:junit-ktx:1.1.3"
+    androidTestImplementation "androidx.compose.ui:ui-test-junit4:$jetpack_compose_version"
+    androidTestImplementation "com.google.truth:truth:1.1.3"
+    androidTestImplementation "org.mockito:mockito-android:3.4.6"
     androidTestDebugImplementation "androidx.compose.ui:ui-test-manifest:$jetpack_compose_version"
 }
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt
new file mode 100644
index 0000000..9419161
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryRepositoryTest.kt
@@ -0,0 +1,146 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.framework.common
+
+import android.content.Context
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SettingsEntryRepositoryTest {
+    private val context: Context = ApplicationProvider.getApplicationContext()
+    private val spaEnvironment = SpaEnvironmentForTest(context)
+    private val entryRepository by spaEnvironment.entryRepository
+
+    @Test
+    fun testGetPageWithEntry() {
+        val pageWithEntry = entryRepository.getAllPageWithEntry()
+        assertThat(pageWithEntry.size).isEqualTo(3)
+        assertThat(
+            entryRepository.getPageWithEntry(getUniquePageId("SppHome"))
+                ?.entries?.size
+        ).isEqualTo(1)
+        assertThat(
+            entryRepository.getPageWithEntry(getUniquePageId("SppLayer1"))
+                ?.entries?.size
+        ).isEqualTo(3)
+        assertThat(
+            entryRepository.getPageWithEntry(getUniquePageId("SppLayer2"))
+                ?.entries?.size
+        ).isEqualTo(2)
+        assertThat(entryRepository.getPageWithEntry(getUniquePageId("SppWithParam"))).isNull()
+    }
+
+    @Test
+    fun testGetEntry() {
+        val entry = entryRepository.getAllEntries()
+        assertThat(entry.size).isEqualTo(7)
+        assertThat(
+            entryRepository.getEntry(
+                getUniqueEntryId(
+                    "ROOT",
+                    SppHome.createSettingsPage(),
+                    SettingsPage.createNull(),
+                    SppHome.createSettingsPage(),
+                )
+            )
+        ).isNotNull()
+        assertThat(
+            entryRepository.getEntry(
+                getUniqueEntryId(
+                    "INJECT",
+                    SppLayer1.createSettingsPage(),
+                    SppHome.createSettingsPage(),
+                    SppLayer1.createSettingsPage(),
+                )
+            )
+        ).isNotNull()
+        assertThat(
+            entryRepository.getEntry(
+                getUniqueEntryId(
+                    "INJECT",
+                    SppLayer2.createSettingsPage(),
+                    SppLayer1.createSettingsPage(),
+                    SppLayer2.createSettingsPage(),
+                )
+            )
+        ).isNotNull()
+        assertThat(
+            entryRepository.getEntry(
+                getUniqueEntryId("Layer1Entry1", SppLayer1.createSettingsPage())
+            )
+        ).isNotNull()
+        assertThat(
+            entryRepository.getEntry(
+                getUniqueEntryId("Layer1Entry2", SppLayer1.createSettingsPage())
+            )
+        ).isNotNull()
+        assertThat(
+            entryRepository.getEntry(
+                getUniqueEntryId("Layer2Entry1", SppLayer2.createSettingsPage())
+            )
+        ).isNotNull()
+        assertThat(
+            entryRepository.getEntry(
+                getUniqueEntryId("Layer2Entry2", SppLayer2.createSettingsPage())
+            )
+        ).isNotNull()
+    }
+
+    @Test
+    fun testGetEntryPath() {
+        assertThat(
+            entryRepository.getEntryPathWithDisplayName(
+                getUniqueEntryId("Layer2Entry1", SppLayer2.createSettingsPage())
+            )
+        ).containsExactly("Layer2Entry1", "INJECT_SppLayer2", "INJECT_SppLayer1", "ROOT_SppHome")
+            .inOrder()
+
+        assertThat(
+            entryRepository.getEntryPathWithTitle(
+                getUniqueEntryId("Layer2Entry2", SppLayer2.createSettingsPage()),
+                "entryTitle"
+            )
+        ).containsExactly("entryTitle", "SppLayer2", "TitleLayer1", "TitleHome").inOrder()
+
+        assertThat(
+            entryRepository.getEntryPathWithDisplayName(
+                getUniqueEntryId(
+                    "INJECT",
+                    SppLayer1.createSettingsPage(),
+                    SppHome.createSettingsPage(),
+                    SppLayer1.createSettingsPage(),
+                )
+            )
+        ).containsExactly("INJECT_SppLayer1", "ROOT_SppHome").inOrder()
+
+        assertThat(
+            entryRepository.getEntryPathWithTitle(
+                getUniqueEntryId(
+                    "INJECT",
+                    SppLayer2.createSettingsPage(),
+                    SppLayer1.createSettingsPage(),
+                    SppLayer2.createSettingsPage(),
+                ),
+                "defaultTitle"
+            )
+        ).containsExactly("SppLayer2", "TitleLayer1", "TitleHome").inOrder()
+    }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt
new file mode 100644
index 0000000..31d2ae4
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsEntryTest.kt
@@ -0,0 +1,160 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.framework.common
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.core.os.bundleOf
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+const val INJECT_ENTRY_NAME = "INJECT"
+const val ROOT_ENTRY_NAME = "ROOT"
+
+class MacroForTest(private val pageId: String, private val entryId: String) : EntryMacro {
+    @Composable
+    override fun UiLayout() {
+        val entryData = LocalEntryDataProvider.current
+        assertThat(entryData.isHighlighted).isFalse()
+        assertThat(entryData.pageId).isEqualTo(pageId)
+        assertThat(entryData.entryId).isEqualTo(entryId)
+    }
+
+    override fun getSearchData(): EntrySearchData {
+        return EntrySearchData("myTitle")
+    }
+
+    override fun getStatusData(): EntryStatusData {
+        return EntryStatusData(isDisabled = true, isSwitchOff = true)
+    }
+}
+
+@RunWith(AndroidJUnit4::class)
+class SettingsEntryTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    @Test
+    fun testBuildBasic() {
+        val owner = SettingsPage.create("mySpp")
+        val entry = SettingsEntryBuilder.create(owner, "myEntry").build()
+        assertThat(entry.id).isEqualTo(getUniqueEntryId("myEntry", owner))
+        assertThat(entry.displayName).isEqualTo("myEntry")
+        assertThat(entry.owner.sppName).isEqualTo("mySpp")
+        assertThat(entry.owner.displayName).isEqualTo("mySpp")
+        assertThat(entry.fromPage).isNull()
+        assertThat(entry.toPage).isNull()
+        assertThat(entry.isAllowSearch).isFalse()
+        assertThat(entry.isSearchDataDynamic).isFalse()
+        assertThat(entry.mutableStatus).isFalse()
+    }
+
+    @Test
+    fun testBuildWithLink() {
+        val owner = SettingsPage.create("mySpp")
+        val fromPage = SettingsPage.create("fromSpp")
+        val toPage = SettingsPage.create("toSpp")
+        val entryFrom = SettingsEntryBuilder.createLinkFrom("myEntry", owner)
+            .setLink(toPage = toPage).build()
+        assertThat(entryFrom.id).isEqualTo(getUniqueEntryId("myEntry", owner, owner, toPage))
+        assertThat(entryFrom.displayName).isEqualTo("myEntry")
+        assertThat(entryFrom.fromPage!!.sppName).isEqualTo("mySpp")
+        assertThat(entryFrom.toPage!!.sppName).isEqualTo("toSpp")
+
+        val entryTo = SettingsEntryBuilder.createLinkTo("myEntry", owner)
+            .setLink(fromPage = fromPage).build()
+        assertThat(entryTo.id).isEqualTo(getUniqueEntryId("myEntry", owner, fromPage, owner))
+        assertThat(entryTo.displayName).isEqualTo("myEntry")
+        assertThat(entryTo.fromPage!!.sppName).isEqualTo("fromSpp")
+        assertThat(entryTo.toPage!!.sppName).isEqualTo("mySpp")
+    }
+
+    @Test
+    fun testBuildInject() {
+        val owner = SettingsPage.create("mySpp")
+        val entryInject = SettingsEntryBuilder.createInject(owner).build()
+        assertThat(entryInject.id).isEqualTo(
+            getUniqueEntryId(
+                INJECT_ENTRY_NAME,
+                owner,
+                toPage = owner
+            )
+        )
+        assertThat(entryInject.displayName).isEqualTo("${INJECT_ENTRY_NAME}_mySpp")
+        assertThat(entryInject.fromPage).isNull()
+        assertThat(entryInject.toPage).isNotNull()
+    }
+
+    @Test
+    fun testBuildRoot() {
+        val owner = SettingsPage.create("mySpp")
+        val entryInject = SettingsEntryBuilder.createRoot(owner, "myRootEntry").build()
+        assertThat(entryInject.id).isEqualTo(
+            getUniqueEntryId(
+                ROOT_ENTRY_NAME,
+                owner,
+                toPage = owner
+            )
+        )
+        assertThat(entryInject.displayName).isEqualTo("myRootEntry")
+        assertThat(entryInject.fromPage).isNull()
+        assertThat(entryInject.toPage).isNotNull()
+    }
+
+    @Test
+    fun testSetAttributes() {
+        val owner = SettingsPage.create("mySpp")
+        val entry = SettingsEntryBuilder.create(owner, "myEntry")
+            .setDisplayName("myEntryDisplay")
+            .setIsAllowSearch(true)
+            .setIsSearchDataDynamic(false)
+            .setHasMutableStatus(true)
+            .build()
+        assertThat(entry.id).isEqualTo(getUniqueEntryId("myEntry", owner))
+        assertThat(entry.displayName).isEqualTo("myEntryDisplay")
+        assertThat(entry.fromPage).isNull()
+        assertThat(entry.toPage).isNull()
+        assertThat(entry.isAllowSearch).isTrue()
+        assertThat(entry.isSearchDataDynamic).isFalse()
+        assertThat(entry.mutableStatus).isTrue()
+    }
+
+    @Test
+    fun testSetMarco() {
+        val owner = SettingsPage.create("mySpp", arguments = bundleOf("param" to "v1"))
+        val entry = SettingsEntryBuilder.create(owner, "myEntry")
+            .setMacro {
+                assertThat(it?.getString("param")).isEqualTo("v1")
+                assertThat(it?.getString("rtParam")).isEqualTo("v2")
+                assertThat(it?.getString("unknown")).isNull()
+                MacroForTest(getUniquePageId("mySpp"), getUniqueEntryId("myEntry", owner))
+            }
+            .build()
+
+        val rtArguments = bundleOf("rtParam" to "v2")
+        composeTestRule.setContent { entry.UiLayout(rtArguments) }
+        val searchData = entry.getSearchData(rtArguments)
+        val statusData = entry.getStatusData(rtArguments)
+        assertThat(searchData?.title).isEqualTo("myTitle")
+        assertThat(searchData?.keyword).isEmpty()
+        assertThat(statusData?.isDisabled).isTrue()
+        assertThat(statusData?.isSwitchOff).isTrue()
+    }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageProviderRepositoryTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageProviderRepositoryTest.kt
new file mode 100644
index 0000000..6c0c652
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageProviderRepositoryTest.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.framework.common
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SettingsPageProviderRepositoryTest {
+    @Test
+    fun getStartPageTest() {
+        val sppRepoEmpty = SettingsPageProviderRepository(emptyList(), emptyList())
+        assertThat(sppRepoEmpty.getDefaultStartPage()).isEqualTo("")
+        assertThat(sppRepoEmpty.getAllRootPages()).isEmpty()
+
+        val sppRepoNull =
+            SettingsPageProviderRepository(emptyList(), listOf(SettingsPage.createNull()))
+        assertThat(sppRepoNull.getDefaultStartPage()).isEqualTo("NULL")
+        assertThat(sppRepoNull.getAllRootPages()).contains(SettingsPage.createNull())
+
+        val rootPage1 = SettingsPage.create(name = "Spp1", displayName = "Spp1")
+        val rootPage2 = SettingsPage.create(name = "Spp2", displayName = "Spp2")
+        val sppRepo = SettingsPageProviderRepository(emptyList(), listOf(rootPage1, rootPage2))
+        val allRoots = sppRepo.getAllRootPages()
+        assertThat(sppRepo.getDefaultStartPage()).isEqualTo("Spp1")
+        assertThat(allRoots.size).isEqualTo(2)
+        assertThat(allRoots).contains(rootPage1)
+        assertThat(allRoots).contains(rootPage2)
+    }
+
+    @Test
+    fun getProviderTest() {
+        val sppRepoEmpty = SettingsPageProviderRepository(emptyList(), emptyList())
+        assertThat(sppRepoEmpty.getAllProviders()).isEmpty()
+        assertThat(sppRepoEmpty.getProviderOrNull("Spp")).isNull()
+
+        val sppRepo = SettingsPageProviderRepository(listOf(
+            object : SettingsPageProvider {
+                override val name = "Spp"
+            }
+        ), emptyList())
+        assertThat(sppRepo.getAllProviders().size).isEqualTo(1)
+        assertThat(sppRepo.getProviderOrNull("Spp")).isNotNull()
+        assertThat(sppRepo.getProviderOrNull("SppUnknown")).isNull()
+    }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt
new file mode 100644
index 0000000..539e56b
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SettingsPageTest.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.framework.common
+
+import android.content.Context
+import androidx.core.os.bundleOf
+import androidx.navigation.NavType
+import androidx.navigation.navArgument
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SettingsPageTest {
+    private val context: Context = ApplicationProvider.getApplicationContext()
+    private val spaLogger = SpaLoggerForTest()
+    private val spaEnvironment = SpaEnvironmentForTest(context, logger = spaLogger)
+
+    @Test
+    fun testNullPage() {
+        val page = SettingsPage.createNull()
+        assertThat(page.id).isEqualTo(getUniquePageId("NULL"))
+        assertThat(page.sppName).isEqualTo("NULL")
+        assertThat(page.displayName).isEqualTo("NULL")
+        assertThat(page.buildRoute()).isEqualTo("NULL")
+        assertThat(page.isCreateBy("NULL")).isTrue()
+        assertThat(page.isCreateBy("Spp")).isFalse()
+        assertThat(page.hasRuntimeParam()).isFalse()
+        assertThat(page.isBrowsable(context, MockActivity::class.java)).isFalse()
+        assertThat(page.createBrowseIntent(context, MockActivity::class.java)).isNull()
+        assertThat(page.createBrowseAdbCommand(context, MockActivity::class.java)).isNull()
+    }
+
+    @Test
+    fun testRegularPage() {
+        val page = SettingsPage.create("mySpp", "SppDisplayName")
+        assertThat(page.id).isEqualTo(getUniquePageId("mySpp"))
+        assertThat(page.sppName).isEqualTo("mySpp")
+        assertThat(page.displayName).isEqualTo("SppDisplayName")
+        assertThat(page.buildRoute()).isEqualTo("mySpp")
+        assertThat(page.isCreateBy("NULL")).isFalse()
+        assertThat(page.isCreateBy("mySpp")).isTrue()
+        assertThat(page.hasRuntimeParam()).isFalse()
+        assertThat(page.isBrowsable(context, MockActivity::class.java)).isTrue()
+        assertThat(page.createBrowseIntent(context, MockActivity::class.java)).isNotNull()
+        assertThat(page.createBrowseAdbCommand(context, MockActivity::class.java)).contains(
+            "-e spaActivityDestination mySpp"
+        )
+    }
+
+    @Test
+    fun testParamPage() {
+        val arguments = bundleOf(
+            "string_param" to "myStr",
+            "int_param" to 10,
+        )
+        val page = spaEnvironment.createPage("SppWithParam", arguments)
+        assertThat(page.id).isEqualTo(getUniquePageId("SppWithParam", listOf(
+            navArgument("string_param") { type = NavType.StringType },
+            navArgument("int_param") { type = NavType.IntType },
+        ), arguments))
+        assertThat(page.sppName).isEqualTo("SppWithParam")
+        assertThat(page.displayName).isEqualTo("SppWithParam")
+        assertThat(page.buildRoute()).isEqualTo("SppWithParam/myStr/10")
+        assertThat(page.isCreateBy("SppWithParam")).isTrue()
+        assertThat(page.hasRuntimeParam()).isFalse()
+        assertThat(page.isBrowsable(context, MockActivity::class.java)).isTrue()
+        assertThat(page.createBrowseIntent(context, MockActivity::class.java)).isNotNull()
+        assertThat(page.createBrowseAdbCommand(context, MockActivity::class.java)).contains(
+            "-e spaActivityDestination SppWithParam/myStr/10"
+        )
+    }
+
+    @Test
+    fun testRtParamPage() {
+        val arguments = bundleOf(
+            "string_param" to "myStr",
+            "int_param" to 10,
+            "rt_param" to "rtStr",
+        )
+        val page = spaEnvironment.createPage("SppWithRtParam", arguments)
+        assertThat(page.id).isEqualTo(getUniquePageId("SppWithRtParam", listOf(
+            navArgument("string_param") { type = NavType.StringType },
+            navArgument("int_param") { type = NavType.IntType },
+            navArgument("rt_param") { type = NavType.StringType },
+        ), arguments))
+        assertThat(page.sppName).isEqualTo("SppWithRtParam")
+        assertThat(page.displayName).isEqualTo("SppWithRtParam")
+        assertThat(page.buildRoute()).isEqualTo("SppWithRtParam/myStr/10/rtStr")
+        assertThat(page.isCreateBy("SppWithRtParam")).isTrue()
+        assertThat(page.hasRuntimeParam()).isTrue()
+        assertThat(page.isBrowsable(context, MockActivity::class.java)).isFalse()
+        assertThat(page.createBrowseIntent(context, MockActivity::class.java)).isNull()
+        assertThat(page.createBrowseAdbCommand(context, MockActivity::class.java)).isNull()
+    }
+
+    @Test
+    fun testPageEvent() {
+        spaLogger.reset()
+        SpaEnvironmentFactory.reset(spaEnvironment)
+        val page = spaEnvironment.createPage("SppHome")
+        page.enterPage()
+        page.leavePage()
+        page.enterPage()
+        assertThat(spaLogger.getEventCount(page.id, LogEvent.PAGE_ENTER, LogCategory.FRAMEWORK))
+            .isEqualTo(2)
+        assertThat(spaLogger.getEventCount(page.id, LogEvent.PAGE_LEAVE, LogCategory.FRAMEWORK))
+            .isEqualTo(1)
+    }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SpaEnvironmentForTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SpaEnvironmentForTest.kt
new file mode 100644
index 0000000..b8731a3
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/SpaEnvironmentForTest.kt
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.framework.common
+
+import android.app.Activity
+import android.content.Context
+import android.os.Bundle
+import androidx.navigation.NavType
+import androidx.navigation.navArgument
+import com.android.settingslib.spa.framework.BrowseActivity
+
+class SpaLoggerForTest : SpaLogger {
+    data class MsgCountKey(val msg: String, val category: LogCategory)
+    data class EventCountKey(val id: String, val event: LogEvent, val category: LogCategory)
+
+    private val messageCount: MutableMap<MsgCountKey, Int> = mutableMapOf()
+    private val eventCount: MutableMap<EventCountKey, Int> = mutableMapOf()
+
+    override fun message(tag: String, msg: String, category: LogCategory) {
+        val key = MsgCountKey("[$tag]$msg", category)
+        messageCount[key] = messageCount.getOrDefault(key, 0) + 1
+    }
+
+    override fun event(id: String, event: LogEvent, category: LogCategory, details: String?) {
+        val key = EventCountKey(id, event, category)
+        eventCount[key] = eventCount.getOrDefault(key, 0) + 1
+    }
+
+    fun getMessageCount(tag: String, msg: String, category: LogCategory): Int {
+        val key = MsgCountKey("[$tag]$msg", category)
+        return messageCount.getOrDefault(key, 0)
+    }
+
+    fun getEventCount(id: String, event: LogEvent, category: LogCategory): Int {
+        val key = EventCountKey(id, event, category)
+        return eventCount.getOrDefault(key, 0)
+    }
+
+    fun reset() {
+        messageCount.clear()
+        eventCount.clear()
+    }
+}
+
+class MockActivity : BrowseActivity()
+
+object SppHome : SettingsPageProvider {
+    override val name = "SppHome"
+
+    override fun getTitle(arguments: Bundle?): String {
+        return "TitleHome"
+    }
+
+    override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+        val owner = this.createSettingsPage()
+        return listOf(
+            SppLayer1.buildInject().setLink(fromPage = owner).build(),
+        )
+    }
+}
+
+object SppLayer1 : SettingsPageProvider {
+    override val name = "SppLayer1"
+
+    override fun getTitle(arguments: Bundle?): String {
+        return "TitleLayer1"
+    }
+
+    fun buildInject(): SettingsEntryBuilder {
+        return SettingsEntryBuilder.createInject(this.createSettingsPage())
+    }
+
+    override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+        val owner = this.createSettingsPage()
+        return listOf(
+            SettingsEntryBuilder.create(owner, "Layer1Entry1").build(),
+            SppLayer2.buildInject().setLink(fromPage = owner).build(),
+            SettingsEntryBuilder.create(owner, "Layer1Entry2").build(),
+        )
+    }
+}
+
+object SppLayer2 : SettingsPageProvider {
+    override val name = "SppLayer2"
+
+    fun buildInject(): SettingsEntryBuilder {
+        return SettingsEntryBuilder.createInject(this.createSettingsPage())
+    }
+
+    override fun buildEntry(arguments: Bundle?): List<SettingsEntry> {
+        val owner = this.createSettingsPage()
+        return listOf(
+            SettingsEntryBuilder.create(owner, "Layer2Entry1").build(),
+            SettingsEntryBuilder.create(owner, "Layer2Entry2").build(),
+        )
+    }
+}
+
+class SpaEnvironmentForTest(
+    context: Context,
+    override val browseActivityClass: Class<out Activity>? = MockActivity::class.java,
+    override val logger: SpaLogger = SpaLoggerForTest()
+) : SpaEnvironment(context) {
+
+    override val pageProviderRepository = lazy {
+        SettingsPageProviderRepository(
+            listOf(
+                SppHome, SppLayer1, SppLayer2,
+                object : SettingsPageProvider {
+                    override val name = "SppWithParam"
+                    override val parameter = listOf(
+                        navArgument("string_param") { type = NavType.StringType },
+                        navArgument("int_param") { type = NavType.IntType },
+                    )
+                },
+                object : SettingsPageProvider {
+                    override val name = "SppWithRtParam"
+                    override val parameter = listOf(
+                        navArgument("string_param") { type = NavType.StringType },
+                        navArgument("int_param") { type = NavType.IntType },
+                        navArgument("rt_param") { type = NavType.StringType },
+                    )
+                },
+            ),
+            listOf(SettingsPage.create("SppHome"))
+        )
+    }
+
+    fun createPage(sppName: String, arguments: Bundle? = null): SettingsPage {
+        return pageProviderRepository.value
+            .getProviderOrNull(sppName)!!.createSettingsPage(arguments)
+    }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/UniqueIdHelper.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/UniqueIdHelper.kt
new file mode 100644
index 0000000..93f9afe
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/common/UniqueIdHelper.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.framework.common
+
+import android.os.Bundle
+import androidx.navigation.NamedNavArgument
+import com.android.settingslib.spa.framework.util.normalize
+
+fun getUniquePageId(
+    name: String,
+    parameter: List<NamedNavArgument> = emptyList(),
+    arguments: Bundle? = null
+): String {
+    val normArguments = parameter.normalize(arguments)
+    return "$name:${normArguments?.toString()}".toHashId()
+}
+
+fun getUniquePageId(page: SettingsPage): String {
+    return getUniquePageId(page.sppName, page.parameter, page.arguments)
+}
+
+fun getUniqueEntryId(
+    name: String,
+    owner: SettingsPage,
+    fromPage: SettingsPage? = null,
+    toPage: SettingsPage? = null
+): String {
+    val ownerId = getUniquePageId(owner)
+    val fromId = if (fromPage == null) "null" else getUniquePageId(fromPage)
+    val toId = if (toPage == null) "null" else getUniquePageId(toPage)
+    return "$name:$ownerId($fromId-$toId)".toHashId()
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/theme/SettingsThemeTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/theme/SettingsThemeTest.kt
new file mode 100644
index 0000000..2ff3039
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/theme/SettingsThemeTest.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.framework.theme
+
+import android.content.Context
+import android.content.res.Resources
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Typography
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.text.font.FontFamily
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.anyInt
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.Mockito.`when` as whenever
+
+@RunWith(AndroidJUnit4::class)
+class SettingsThemeTest {
+    @get:Rule
+    val mockito: MockitoRule = MockitoJUnit.rule()
+
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    @Mock
+    private lateinit var context: Context
+
+    @Mock
+    private lateinit var resources: Resources
+
+    private var nextMockResId = 1
+
+    @Before
+    fun setUp() {
+        whenever(context.resources).thenReturn(resources)
+        whenever(resources.getString(anyInt())).thenReturn("")
+    }
+
+    private fun mockAndroidConfig(configName: String, configValue: String) {
+        whenever(resources.getIdentifier(configName, "string", "android"))
+            .thenReturn(nextMockResId)
+        whenever(resources.getString(nextMockResId)).thenReturn(configValue)
+        nextMockResId++
+    }
+
+    @Test
+    fun noFontFamilyConfig_useDefaultFontFamily() {
+        val fontFamily = getFontFamily()
+
+        assertThat(fontFamily.headlineLarge.fontFamily).isSameInstanceAs(FontFamily.Default)
+        assertThat(fontFamily.bodyLarge.fontFamily).isSameInstanceAs(FontFamily.Default)
+    }
+
+    @Test
+    fun hasFontFamilyConfig_useConfiguredFontFamily() {
+        mockAndroidConfig("config_headlineFontFamily", "HeadlineNormal")
+        mockAndroidConfig("config_headlineFontFamilyMedium", "HeadlineMedium")
+        mockAndroidConfig("config_bodyFontFamily", "BodyNormal")
+        mockAndroidConfig("config_bodyFontFamilyMedium", "BodyMedium")
+
+        val fontFamily = getFontFamily()
+
+        val headlineFontFamily = fontFamily.headlineLarge.fontFamily.toString()
+        assertThat(headlineFontFamily).contains("HeadlineNormal")
+        assertThat(headlineFontFamily).contains("HeadlineMedium")
+        val bodyFontFamily = fontFamily.bodyLarge.fontFamily.toString()
+        assertThat(bodyFontFamily).contains("BodyNormal")
+        assertThat(bodyFontFamily).contains("BodyMedium")
+    }
+
+    private fun getFontFamily(): Typography {
+        lateinit var typography: Typography
+        composeTestRule.setContent {
+            CompositionLocalProvider(LocalContext provides context) {
+                SettingsTheme {
+                    typography = MaterialTheme.typography
+                }
+            }
+        }
+        return typography
+    }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/ParameterTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/ParameterTest.kt
new file mode 100644
index 0000000..48ebd8d
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/framework/util/ParameterTest.kt
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.framework.util
+
+import androidx.core.os.bundleOf
+import androidx.navigation.NamedNavArgument
+import androidx.navigation.NavType
+import androidx.navigation.navArgument
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class ParameterTest {
+    @Test
+    fun navRouteTest() {
+        val navArguments = listOf(
+            navArgument("string_param") { type = NavType.StringType },
+            navArgument("int_param") { type = NavType.IntType },
+        )
+
+        val route = navArguments.navRoute()
+        assertThat(route).isEqualTo("/{string_param}/{int_param}")
+    }
+
+    @Test
+    fun navLinkTest() {
+        val navArguments = listOf(
+            navArgument("string_param") { type = NavType.StringType },
+            navArgument("int_param") { type = NavType.IntType },
+        )
+
+        val unsetAllLink = navArguments.navLink()
+        assertThat(unsetAllLink).isEqualTo("/[unset]/[unset]")
+
+        val setAllLink = navArguments.navLink(
+            bundleOf(
+                "string_param" to "myStr",
+                "int_param" to 10,
+            )
+        )
+        assertThat(setAllLink).isEqualTo("/myStr/10")
+
+        val setUnknownLink = navArguments.navLink(
+            bundleOf(
+                "string_param" to "myStr",
+                "int_param" to 10,
+                "unknown_param" to "unknown",
+            )
+        )
+        assertThat(setUnknownLink).isEqualTo("/myStr/10")
+
+        val setWrongTypeLink = navArguments.navLink(
+            bundleOf(
+                "string_param" to "myStr",
+                "int_param" to "wrongStr",
+            )
+        )
+        assertThat(setWrongTypeLink).isEqualTo("/myStr/0")
+    }
+
+    @Test
+    fun normalizeTest() {
+        val emptyArguments = emptyList<NamedNavArgument>()
+        assertThat(emptyArguments.normalize()).isNull()
+
+        val navArguments = listOf(
+            navArgument("string_param") { type = NavType.StringType },
+            navArgument("int_param") { type = NavType.IntType },
+            navArgument("rt_param") { type = NavType.StringType },
+        )
+
+        val emptyParam = navArguments.normalize()
+        assertThat(emptyParam).isNotNull()
+        assertThat(emptyParam.toString()).isEqualTo(
+            "Bundle[{rt_param=null, unset_string_param=null, unset_int_param=null}]"
+        )
+
+        val setPartialParam = navArguments.normalize(
+            bundleOf(
+                "string_param" to "myStr",
+                "rt_param" to "rtStr",
+            )
+        )
+        assertThat(setPartialParam).isNotNull()
+        assertThat(setPartialParam.toString()).isEqualTo(
+            "Bundle[{rt_param=null, string_param=myStr, unset_int_param=null}]"
+        )
+
+        val setAllParam = navArguments.normalize(
+            bundleOf(
+                "string_param" to "myStr",
+                "int_param" to 10,
+                "rt_param" to "rtStr",
+            )
+        )
+        assertThat(setAllParam).isNotNull()
+        assertThat(setAllParam.toString()).isEqualTo(
+            "Bundle[{rt_param=null, int_param=10, string_param=myStr}]"
+        )
+    }
+
+    @Test
+    fun getArgTest() {
+        val navArguments = listOf(
+            navArgument("string_param") { type = NavType.StringType },
+            navArgument("int_param") { type = NavType.IntType },
+        )
+
+        assertThat(
+            navArguments.getStringArg(
+                "string_param", bundleOf(
+                    "string_param" to "myStr",
+                )
+            )
+        ).isEqualTo("myStr")
+
+        assertThat(
+            navArguments.getStringArg(
+                "string_param", bundleOf(
+                    "string_param" to 10,
+                )
+            )
+        ).isNull()
+
+        assertThat(
+            navArguments.getStringArg(
+                "unknown_param", bundleOf(
+                    "string_param" to "myStr",
+                )
+            )
+        ).isNull()
+
+        assertThat(navArguments.getStringArg("string_param")).isNull()
+
+        assertThat(
+            navArguments.getIntArg(
+                "int_param", bundleOf(
+                    "int_param" to 10,
+                )
+            )
+        ).isEqualTo(10)
+
+        assertThat(
+            navArguments.getIntArg(
+                "int_param", bundleOf(
+                    "int_param" to "10",
+                )
+            )
+        ).isEqualTo(0)
+
+        assertThat(
+            navArguments.getIntArg(
+                "unknown_param", bundleOf(
+                    "int_param" to 10,
+                )
+            )
+        ).isNull()
+
+        assertThat(navArguments.getIntArg("int_param")).isNull()
+    }
+
+    @Test
+    fun isRuntimeParamTest() {
+        val regularParam = navArgument("regular_param") { type = NavType.StringType }
+        val rtParam = navArgument("rt_param") { type = NavType.StringType }
+        assertThat(regularParam.isRuntimeParam()).isFalse()
+        assertThat(rtParam.isRuntimeParam()).isTrue()
+    }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ChartTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ChartTest.kt
new file mode 100644
index 0000000..fa7a98a
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/ChartTest.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.widget
+
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.semantics.SemanticsPropertyKey
+import androidx.compose.ui.semantics.SemanticsPropertyReceiver
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.test.SemanticsMatcher
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.widget.chart.BarChart
+import com.android.settingslib.spa.widget.chart.BarChartData
+import com.android.settingslib.spa.widget.chart.LineChart
+import com.android.settingslib.spa.widget.chart.LineChartData
+import com.android.settingslib.spa.widget.chart.PieChart
+import com.android.settingslib.spa.widget.chart.PieChartData
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class ChartTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    private val Chart = SemanticsPropertyKey<String>("Chart")
+    private var SemanticsPropertyReceiver.chart by Chart
+    private fun hasChart(chart: String): SemanticsMatcher =
+        SemanticsMatcher.expectValue(Chart, chart)
+
+    @Test
+    fun line_chart_displayed() {
+        composeTestRule.setContent {
+            LineChart(
+                chartDataList = listOf(
+                    LineChartData(x = 0f, y = 0f),
+                    LineChartData(x = 1f, y = 0.1f),
+                    LineChartData(x = 2f, y = 0.2f),
+                    LineChartData(x = 3f, y = 0.7f),
+                    LineChartData(x = 4f, y = 0.9f),
+                    LineChartData(x = 5f, y = 1.0f),
+                    LineChartData(x = 6f, y = 0.8f),
+                ),
+                modifier = Modifier.semantics { chart = "line" }
+            )
+        }
+
+        composeTestRule.onNode(hasChart("line")).assertIsDisplayed()
+    }
+
+    @Test
+    fun bar_chart_displayed() {
+        composeTestRule.setContent {
+            BarChart(
+                chartDataList = listOf(
+                    BarChartData(x = 0f, y = 12f),
+                    BarChartData(x = 1f, y = 5f),
+                    BarChartData(x = 2f, y = 21f),
+                    BarChartData(x = 3f, y = 5f),
+                    BarChartData(x = 4f, y = 10f),
+                    BarChartData(x = 5f, y = 9f),
+                    BarChartData(x = 6f, y = 1f),
+                ),
+                yAxisMaxValue = 30f,
+                modifier = Modifier.semantics { chart = "bar" }
+            )
+        }
+
+        composeTestRule.onNode(hasChart("bar")).assertIsDisplayed()
+    }
+
+    @Test
+    fun pie_chart_displayed() {
+        composeTestRule.setContent {
+            PieChart(
+                chartDataList = listOf(
+                    PieChartData(label = "Settings", value = 20f),
+                    PieChartData(label = "Chrome", value = 5f),
+                    PieChartData(label = "Gmail", value = 3f),
+                ),
+                centerText = "Today",
+                modifier = Modifier.semantics { chart = "pie" }
+            )
+        }
+
+        composeTestRule.onNode(hasChart("pie")).assertIsDisplayed()
+    }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/RegularScaffoldTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/RegularScaffoldTest.kt
new file mode 100644
index 0000000..1964c43
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/RegularScaffoldTest.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.widget.scaffold
+
+import androidx.compose.material3.Text
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class RegularScaffoldTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    @Test
+    fun regularScaffold_titleIsDisplayed() {
+        composeTestRule.setContent {
+            RegularScaffold(title = TITLE) {
+                Text(text = "AAA")
+                Text(text = "BBB")
+            }
+        }
+
+        composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
+    }
+
+    @Test
+    fun regularScaffold_itemsAreDisplayed() {
+        composeTestRule.setContent {
+            RegularScaffold(title = TITLE) {
+                Text(text = "AAA")
+                Text(text = "BBB")
+            }
+        }
+
+        composeTestRule.onNodeWithText("AAA").assertIsDisplayed()
+        composeTestRule.onNodeWithText("BBB").assertIsDisplayed()
+    }
+
+    private companion object {
+        const val TITLE = "title"
+    }
+}
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SearchScaffoldTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SearchScaffoldTest.kt
index ec3379d..c3e1d54 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SearchScaffoldTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SearchScaffoldTest.kt
@@ -43,7 +43,7 @@
     @Test
     fun initialState_titleIsDisplayed() {
         composeTestRule.setContent {
-            SearchScaffold(title = TITLE) {}
+            SearchScaffold(title = TITLE) { _, _ -> }
         }
 
         composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
@@ -116,7 +116,7 @@
     private fun setContent(): State<String> {
         lateinit var actualSearchQuery: State<String>
         composeTestRule.setContent {
-            SearchScaffold(title = TITLE) { searchQuery ->
+            SearchScaffold(title = TITLE) { _, searchQuery ->
                 SideEffect {
                     actualSearchQuery = searchQuery
                 }
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsPagerKtTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsPagerTest.kt
similarity index 89%
rename from packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsPagerKtTest.kt
rename to packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsPagerTest.kt
index 0c84eac..0c745d5 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsPagerKtTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsPagerTest.kt
@@ -16,7 +16,6 @@
 
 package com.android.settingslib.spa.widget.scaffold
 
-import androidx.compose.runtime.Composable
 import androidx.compose.ui.test.assertIsDisplayed
 import androidx.compose.ui.test.assertIsNotDisplayed
 import androidx.compose.ui.test.assertIsNotSelected
@@ -31,15 +30,13 @@
 import org.junit.runner.RunWith
 
 @RunWith(AndroidJUnit4::class)
-class SettingsPagerKtTest {
+class SettingsPagerTest {
     @get:Rule
     val composeTestRule = createComposeRule()
 
     @Test
     fun twoPage_initialState() {
-        composeTestRule.setContent {
-            TestTwoPage()
-        }
+        setTwoPagesContent()
 
         composeTestRule.onNodeWithText("Personal").assertIsSelected()
         composeTestRule.onNodeWithText("Page 0").assertIsDisplayed()
@@ -49,9 +46,7 @@
 
     @Test
     fun twoPage_afterSwitch() {
-        composeTestRule.setContent {
-            TestTwoPage()
-        }
+        setTwoPagesContent()
 
         composeTestRule.onNodeWithText("Work").performClick()
 
@@ -73,11 +68,12 @@
         composeTestRule.onNodeWithText("Page 0").assertIsDisplayed()
         composeTestRule.onNodeWithText("Page 1").assertDoesNotExist()
     }
-}
 
-@Composable
-private fun TestTwoPage() {
-    SettingsPager(listOf("Personal", "Work")) {
-        SettingsTitle(title = "Page $it")
+    private fun setTwoPagesContent() {
+        composeTestRule.setContent {
+            SettingsPager(listOf("Personal", "Work")) {
+                SettingsTitle(title = "Page $it")
+            }
+        }
     }
 }
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffoldTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffoldTest.kt
new file mode 100644
index 0000000..f042404
--- /dev/null
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/scaffold/SettingsScaffoldTest.kt
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spa.widget.scaffold
+
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.material3.Text
+import androidx.compose.runtime.SideEffect
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class SettingsScaffoldTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    @Test
+    fun settingsScaffold_titleIsDisplayed() {
+        composeTestRule.setContent {
+            SettingsScaffold(title = TITLE) {
+                Text(text = "AAA")
+                Text(text = "BBB")
+            }
+        }
+
+        composeTestRule.onNodeWithText(TITLE).assertIsDisplayed()
+    }
+
+    @Test
+    fun settingsScaffold_itemsAreDisplayed() {
+        composeTestRule.setContent {
+            SettingsScaffold(title = TITLE) {
+                Text(text = "AAA")
+                Text(text = "BBB")
+            }
+        }
+
+        composeTestRule.onNodeWithText("AAA").assertIsDisplayed()
+        composeTestRule.onNodeWithText("BBB").assertIsDisplayed()
+    }
+
+    @Test
+    fun settingsScaffold_noHorizontalPadding() {
+        lateinit var actualPaddingValues: PaddingValues
+
+        composeTestRule.setContent {
+            SettingsScaffold(title = TITLE) { paddingValues ->
+                SideEffect {
+                    actualPaddingValues = paddingValues
+                }
+            }
+        }
+
+        assertThat(actualPaddingValues.calculateLeftPadding(LayoutDirection.Ltr)).isEqualTo(0.dp)
+        assertThat(actualPaddingValues.calculateLeftPadding(LayoutDirection.Rtl)).isEqualTo(0.dp)
+        assertThat(actualPaddingValues.calculateRightPadding(LayoutDirection.Ltr)).isEqualTo(0.dp)
+        assertThat(actualPaddingValues.calculateRightPadding(LayoutDirection.Rtl)).isEqualTo(0.dp)
+    }
+
+    private companion object {
+        const val TITLE = "title"
+    }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/Contexts.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/Contexts.kt
index fd723dd..bb1cd6e 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/Contexts.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/Contexts.kt
@@ -22,6 +22,7 @@
 import android.app.usage.StorageStatsManager
 import android.apphibernation.AppHibernationManager
 import android.content.Context
+import android.content.pm.CrossProfileApps
 import android.content.pm.verify.domain.DomainVerificationManager
 import android.os.UserHandle
 import android.os.UserManager
@@ -36,6 +37,9 @@
 /** The [AppOpsManager] instance. */
 val Context.appOpsManager get() = getSystemService(AppOpsManager::class.java)!!
 
+/** The [CrossProfileApps] instance. */
+val Context.crossProfileApps get() = getSystemService(CrossProfileApps::class.java)!!
+
 /** The [DevicePolicyManager] instance. */
 val Context.devicePolicyManager get() = getSystemService(DevicePolicyManager::class.java)!!
 
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/ApplicationInfos.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/ApplicationInfos.kt
index c1ac5d4..8954d22 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/ApplicationInfos.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/ApplicationInfos.kt
@@ -35,6 +35,9 @@
 /** Checks whether a flag is associated with the application. */
 fun ApplicationInfo.hasFlag(flag: Int): Boolean = (flags and flag) > 0
 
+/** Checks whether the application is currently installed. */
+val ApplicationInfo.installed: Boolean get() = hasFlag(ApplicationInfo.FLAG_INSTALLED)
+
 /** Checks whether the application is disabled until used. */
 val ApplicationInfo.isDisabledUntilUsed: Boolean
     get() = enabledSetting == PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagerExt.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagerExt.kt
new file mode 100644
index 0000000..2b2f11c
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/model/app/PackageManagerExt.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spaprivileged.model.app
+
+import android.content.Intent
+import android.content.pm.ActivityInfo
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.ResolveInfoFlags
+
+/**
+ * Checks if a package is system module.
+ */
+fun PackageManager.isSystemModule(packageName: String): Boolean = try {
+    getModuleInfo(packageName, 0)
+    true
+} catch (_: PackageManager.NameNotFoundException) {
+    // Expected, not system module
+    false
+}
+
+/**
+ * Resolves the activity to start for a given application and action.
+ */
+fun PackageManager.resolveActionForApp(
+    app: ApplicationInfo,
+    action: String,
+    flags: Int = 0,
+): ActivityInfo? {
+    val intent = Intent(action).apply {
+        `package` = app.packageName
+    }
+    return resolveActivityAsUser(intent, ResolveInfoFlags.of(flags.toLong()), app.userId)
+        ?.activityInfo
+}
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
index 408b9df..9d6b311 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppList.kt
@@ -25,12 +25,12 @@
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.unit.Dp
 import androidx.lifecycle.viewmodel.compose.viewModel
 import com.android.settingslib.spa.framework.compose.LogCompositions
 import com.android.settingslib.spa.framework.compose.TimeMeasurer.Companion.rememberTimeMeasurer
 import com.android.settingslib.spa.framework.compose.rememberLazyListStateAndHideKeyboardWhenStartScroll
 import com.android.settingslib.spa.framework.compose.toState
-import com.android.settingslib.spa.framework.theme.SettingsDimension
 import com.android.settingslib.spa.widget.ui.PlaceholderTitle
 import com.android.settingslib.spaprivileged.R
 import com.android.settingslib.spaprivileged.model.app.AppListConfig
@@ -41,6 +41,13 @@
 import kotlinx.coroutines.Dispatchers
 
 private const val TAG = "AppList"
+private const val CONTENT_TYPE_HEADER = "header"
+
+internal data class AppListState(
+    val showSystem: State<Boolean>,
+    val option: State<Int>,
+    val searchQuery: State<String>,
+)
 
 /**
  * The template to render an App List.
@@ -49,23 +56,28 @@
  */
 @Composable
 internal fun <T : AppRecord> AppList(
-    appListConfig: AppListConfig,
+    config: AppListConfig,
     listModel: AppListModel<T>,
-    showSystem: State<Boolean>,
-    option: State<Int>,
-    searchQuery: State<String>,
+    state: AppListState,
+    header: @Composable () -> Unit,
     appItem: @Composable (itemState: AppListItemModel<T>) -> Unit,
+    bottomPadding: Dp,
+    appListDataSupplier: @Composable () -> State<AppListData<T>?> = {
+        loadAppListData(config, listModel, state)
+    },
 ) {
-    LogCompositions(TAG, appListConfig.userId.toString())
-    val appListData = loadAppEntries(appListConfig, listModel, showSystem, option, searchQuery)
-    AppListWidget(appListData, listModel, appItem)
+    LogCompositions(TAG, config.userId.toString())
+    val appListData = appListDataSupplier()
+    AppListWidget(appListData, listModel, header, appItem, bottomPadding)
 }
 
 @Composable
 private fun <T : AppRecord> AppListWidget(
     appListData: State<AppListData<T>?>,
     listModel: AppListModel<T>,
+    header: @Composable () -> Unit,
     appItem: @Composable (itemState: AppListItemModel<T>) -> Unit,
+    bottomPadding: Dp,
 ) {
     val timeMeasurer = rememberTimeMeasurer(TAG)
     appListData.value?.let { (list, option) ->
@@ -77,8 +89,12 @@
         LazyColumn(
             modifier = Modifier.fillMaxSize(),
             state = rememberLazyListStateAndHideKeyboardWhenStartScroll(),
-            contentPadding = PaddingValues(bottom = SettingsDimension.itemPaddingVertical),
+            contentPadding = PaddingValues(bottom = bottomPadding),
         ) {
+            item(contentType = CONTENT_TYPE_HEADER) {
+                header()
+            }
+
             items(count = list.size, key = { option to list[it].record.app.packageName }) {
                 val appEntry = list[it]
                 val summary = listModel.getSummary(option, appEntry.record) ?: "".toState()
@@ -92,19 +108,17 @@
 }
 
 @Composable
-private fun <T : AppRecord> loadAppEntries(
-    appListConfig: AppListConfig,
+private fun <T : AppRecord> loadAppListData(
+    config: AppListConfig,
     listModel: AppListModel<T>,
-    showSystem: State<Boolean>,
-    option: State<Int>,
-    searchQuery: State<String>,
+    state: AppListState,
 ): State<AppListData<T>?> {
-    val viewModel: AppListViewModel<T> = viewModel(key = appListConfig.userId.toString())
-    viewModel.appListConfig.setIfAbsent(appListConfig)
+    val viewModel: AppListViewModel<T> = viewModel(key = config.userId.toString())
+    viewModel.appListConfig.setIfAbsent(config)
     viewModel.listModel.setIfAbsent(listModel)
-    viewModel.showSystem.Sync(showSystem)
-    viewModel.option.Sync(option)
-    viewModel.searchQuery.Sync(searchQuery)
+    viewModel.showSystem.Sync(state.showSystem)
+    viewModel.option.Sync(state.option)
+    viewModel.searchQuery.Sync(state.searchQuery)
 
     return viewModel.appListDataFlow.collectAsState(null, Dispatchers.Default)
 }
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
index 99376b0..388a7d8 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/template/app/AppListPage.kt
@@ -37,6 +37,8 @@
 
 /**
  * The full screen template for an App List page.
+ *
+ * @param header the description header appears before all the applications.
  */
 @Composable
 fun <T : AppRecord> AppListPage(
@@ -44,6 +46,7 @@
     listModel: AppListModel<T>,
     showInstantApps: Boolean = false,
     primaryUserOnly: Boolean = false,
+    header: @Composable () -> Unit = {},
     appItem: @Composable (itemState: AppListItemModel<T>) -> Unit,
 ) {
     val showSystem = rememberSaveable { mutableStateOf(false) }
@@ -52,22 +55,26 @@
         actions = {
             ShowSystemAction(showSystem.value) { showSystem.value = it }
         },
-    ) { searchQuery ->
+    ) { bottomPadding, searchQuery ->
         WorkProfilePager(primaryUserOnly) { userInfo ->
             Column(Modifier.fillMaxSize()) {
                 val options = remember { listModel.getSpinnerOptions() }
                 val selectedOption = rememberSaveable { mutableStateOf(0) }
                 Spinner(options, selectedOption.value) { selectedOption.value = it }
                 AppList(
-                    appListConfig = AppListConfig(
+                    config = AppListConfig(
                         userId = userInfo.id,
                         showInstantApps = showInstantApps,
                     ),
                     listModel = listModel,
-                    showSystem = showSystem,
-                    option = selectedOption,
-                    searchQuery = searchQuery,
+                    state = AppListState(
+                        showSystem = showSystem,
+                        option = selectedOption,
+                        searchQuery = searchQuery,
+                    ),
+                    header = header,
                     appItem = appItem,
+                    bottomPadding = bottomPadding,
                 )
             }
         }
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagerExtTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagerExtTest.kt
new file mode 100644
index 0000000..4207490
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/model/app/PackageManagerExtTest.kt
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spaprivileged.model.app
+
+import android.content.ComponentName
+import android.content.pm.ActivityInfo
+import android.content.pm.ApplicationInfo
+import android.content.pm.ModuleInfo
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.NameNotFoundException
+import android.content.pm.PackageManager.ResolveInfoFlags
+import android.content.pm.ResolveInfo
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Mock
+import org.mockito.Mockito.any
+import org.mockito.Mockito.eq
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.Mockito.`when` as whenever
+
+@RunWith(AndroidJUnit4::class)
+class PackageManagerExtTest {
+    @JvmField
+    @Rule
+    val mockito: MockitoRule = MockitoJUnit.rule()
+
+    @Mock
+    private lateinit var packageManager: PackageManager
+
+    private fun mockResolveActivityAsUser(resolveInfo: ResolveInfo?) {
+        whenever(
+            packageManager.resolveActivityAsUser(any(), any<ResolveInfoFlags>(), eq(APP.userId))
+        ).thenReturn(resolveInfo)
+    }
+
+    @Test
+    fun isSystemModule_whenSystemModule_returnTrue() {
+        whenever(packageManager.getModuleInfo(PACKAGE_NAME, 0)).thenReturn(ModuleInfo())
+
+        val isSystemModule = packageManager.isSystemModule(PACKAGE_NAME)
+
+        assertThat(isSystemModule).isTrue()
+    }
+
+    @Test
+    fun isSystemModule_whenNotSystemModule_returnFalse() {
+        whenever(packageManager.getModuleInfo(PACKAGE_NAME, 0)).thenThrow(NameNotFoundException())
+
+        val isSystemModule = packageManager.isSystemModule(PACKAGE_NAME)
+
+        assertThat(isSystemModule).isFalse()
+    }
+
+    @Test
+    fun resolveActionForApp_noResolveInfo() {
+        mockResolveActivityAsUser(null)
+
+        val activityInfo = packageManager.resolveActionForApp(APP, ACTION)
+
+        assertThat(activityInfo).isNull()
+    }
+
+    @Test
+    fun resolveActionForApp_noActivityInfo() {
+        mockResolveActivityAsUser(ResolveInfo())
+
+        val activityInfo = packageManager.resolveActionForApp(APP, ACTION)
+
+        assertThat(activityInfo).isNull()
+    }
+
+    @Test
+    fun resolveActionForApp_hasActivityInfo() {
+        mockResolveActivityAsUser(ResolveInfo().apply {
+            activityInfo = ActivityInfo().apply {
+                packageName = PACKAGE_NAME
+                name = ACTIVITY_NAME
+            }
+        })
+
+        val activityInfo = packageManager.resolveActionForApp(APP, ACTION)!!
+
+        assertThat(activityInfo.componentName).isEqualTo(ComponentName(PACKAGE_NAME, ACTIVITY_NAME))
+    }
+
+    @Test
+    fun resolveActionForApp_withFlags() {
+        packageManager.resolveActionForApp(
+            app = APP,
+            action = ACTION,
+            flags = PackageManager.GET_META_DATA,
+        )
+
+        val flagsCaptor = ArgumentCaptor.forClass(ResolveInfoFlags::class.java)
+        verify(packageManager).resolveActivityAsUser(any(), flagsCaptor.capture(), eq(APP.userId))
+        assertThat(flagsCaptor.value.value).isEqualTo(PackageManager.GET_META_DATA.toLong())
+    }
+
+    private companion object {
+        const val PACKAGE_NAME = "package.name"
+        const val ACTIVITY_NAME = "ActivityName"
+        const val ACTION = "action"
+        const val UID = 123
+        val APP = ApplicationInfo().apply {
+            packageName = PACKAGE_NAME
+            uid = UID
+        }
+    }
+}
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
new file mode 100644
index 0000000..80c4eac
--- /dev/null
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/template/app/AppListTest.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2022 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.settingslib.spaprivileged.template.app
+
+import android.content.Context
+import android.content.pm.ApplicationInfo
+import android.icu.text.CollationKey
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.unit.dp
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.framework.compose.stateOf
+import com.android.settingslib.spa.framework.compose.toState
+import com.android.settingslib.spa.framework.util.asyncMapItem
+import com.android.settingslib.spaprivileged.R
+import com.android.settingslib.spaprivileged.model.app.AppEntry
+import com.android.settingslib.spaprivileged.model.app.AppListConfig
+import com.android.settingslib.spaprivileged.model.app.AppListData
+import com.android.settingslib.spaprivileged.model.app.AppListModel
+import com.android.settingslib.spaprivileged.model.app.AppRecord
+import kotlinx.coroutines.flow.Flow
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class AppListTest {
+    @get:Rule
+    val composeTestRule = createComposeRule()
+
+    private var context: Context = ApplicationProvider.getApplicationContext()
+
+    @Test
+    fun whenNoApps() {
+        setContent(appEntries = emptyList())
+
+        composeTestRule.onNodeWithText(context.getString(R.string.no_applications))
+            .assertIsDisplayed()
+    }
+
+    @Test
+    fun couldShowAppItem() {
+        setContent(appEntries = listOf(APP_ENTRY))
+
+        composeTestRule.onNodeWithText(APP_ENTRY.label).assertIsDisplayed()
+    }
+
+    @Test
+    fun couldShowHeader() {
+        setContent(header = { Text(HEADER) }, appEntries = listOf(APP_ENTRY))
+
+        composeTestRule.onNodeWithText(HEADER).assertIsDisplayed()
+    }
+
+    private fun setContent(
+        header: @Composable () -> Unit = {},
+        appEntries: List<AppEntry<TestAppRecord>>,
+    ) {
+        composeTestRule.setContent {
+            AppList(
+                config = AppListConfig(userId = USER_ID, showInstantApps = false),
+                listModel = TestAppListModel(),
+                state = AppListState(
+                    showSystem = false.toState(),
+                    option = 0.toState(),
+                    searchQuery = "".toState(),
+                ),
+                header = header,
+                appItem = { AppListItem(it) {} },
+                bottomPadding = 0.dp,
+                appListDataSupplier = {
+                    stateOf(AppListData(appEntries, option = 0))
+                }
+            )
+        }
+    }
+
+    private companion object {
+        const val USER_ID = 0
+        const val HEADER = "Header"
+        val APP_ENTRY = AppEntry(
+            record = TestAppRecord(ApplicationInfo()),
+            label = "AAA",
+            labelCollationKey = CollationKey("", byteArrayOf()),
+        )
+    }
+}
+
+private data class TestAppRecord(override val app: ApplicationInfo) : AppRecord
+
+private class TestAppListModel : AppListModel<TestAppRecord> {
+    override fun transform(userIdFlow: Flow<Int>, appListFlow: Flow<List<ApplicationInfo>>) =
+        appListFlow.asyncMapItem { TestAppRecord(it) }
+
+    @Composable
+    override fun getSummary(option: Int, record: TestAppRecord) = null
+
+    override fun filter(
+        userIdFlow: Flow<Int>,
+        option: Int,
+        recordListFlow: Flow<List<TestAppRecord>>,
+    ) = recordListFlow
+}
diff --git a/packages/SettingsLib/res/values-uz/strings.xml b/packages/SettingsLib/res/values-uz/strings.xml
index 1738e01..efb5eb9 100644
--- a/packages/SettingsLib/res/values-uz/strings.xml
+++ b/packages/SettingsLib/res/values-uz/strings.xml
@@ -548,7 +548,7 @@
     <string name="shared_data_no_blobs_text" msgid="3108114670341737434">"Bu foydalanuvchining umumiy maʼlumotlari topilmadi."</string>
     <string name="shared_data_query_failure_text" msgid="3489828881998773687">"Umumiy maʼlumotlarni yuklashda xatolik yuz berdi. Qayta urining."</string>
     <string name="blob_id_text" msgid="8680078988996308061">"Umumiy maʼlumotlar identifikatori: <xliff:g id="BLOB_ID">%d</xliff:g>"</string>
-    <string name="blob_expires_text" msgid="7882727111491739331">"Amal qilish muddati: <xliff:g id="DATE">%s</xliff:g>"</string>
+    <string name="blob_expires_text" msgid="7882727111491739331">"Muddati: <xliff:g id="DATE">%s</xliff:g>"</string>
     <string name="shared_data_delete_failure_text" msgid="3842701391009628947">"Umumiy maʼlumotlarni oʻchirishda xatolik yuz berdi."</string>
     <string name="shared_data_no_accessors_dialog_text" msgid="8903738462570715315">"Bu umumiy maʼlumotlar yuzasidan kelgan soʻrov topilmadi. Oʻchirib tashlansinmi?"</string>
     <string name="accessor_info_title" msgid="8289823651512477787">"Umumiy maʼlumotlar bor ilovalar"</string>
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
index 6bc1160..dd56bde 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceManager.java
@@ -188,7 +188,6 @@
     /**
      * Updates the Hearing Aid devices; specifically the HiSyncId's. This routine is called when the
      * Hearing Aid Service is connected and the HiSyncId's are now available.
-     * @param LocalBluetoothProfileManager profileManager
      */
     public synchronized void updateHearingAidsDevices() {
         mHearingAidDeviceManager.updateHearingAidsDevices();
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java
new file mode 100644
index 0000000..f06aab3
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java
@@ -0,0 +1,336 @@
+/*
+ * Copyright (C) 2022 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.settingslib.bluetooth;
+
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.bluetooth.BluetoothAdapter;
+import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHapClient;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+import android.util.Log;
+
+import com.android.settingslib.R;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * HapClientProfile handles the Bluetooth HAP service client role.
+ */
+public class HapClientProfile implements LocalBluetoothProfile {
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(flag = true, value = {
+            HearingAidType.TYPE_INVALID,
+            HearingAidType.TYPE_BINAURAL,
+            HearingAidType.TYPE_MONAURAL,
+            HearingAidType.TYPE_BANDED,
+            HearingAidType.TYPE_RFU
+    })
+
+    /** Hearing aid type definition for HAP Client. */
+    public @interface HearingAidType {
+        int TYPE_INVALID = -1;
+        int TYPE_BINAURAL = BluetoothHapClient.TYPE_BINAURAL;
+        int TYPE_MONAURAL = BluetoothHapClient.TYPE_MONAURAL;
+        int TYPE_BANDED = BluetoothHapClient.TYPE_BANDED;
+        int TYPE_RFU = BluetoothHapClient.TYPE_RFU;
+    }
+
+    static final String NAME = "HapClient";
+    private static final String TAG = "HapClientProfile";
+
+    // Order of this profile in device profiles list
+    private static final int ORDINAL = 1;
+
+    private final BluetoothAdapter mBluetoothAdapter;
+    private final CachedBluetoothDeviceManager mDeviceManager;
+    private final LocalBluetoothProfileManager mProfileManager;
+    private BluetoothHapClient mService;
+    private boolean mIsProfileReady;
+
+    // These callbacks run on the main thread.
+    private final class HapClientServiceListener implements BluetoothProfile.ServiceListener {
+
+        @Override
+        public void onServiceConnected(int profile, BluetoothProfile proxy) {
+            mService = (BluetoothHapClient) proxy;
+            // We just bound to the service, so refresh the UI for any connected HapClient devices.
+            List<BluetoothDevice> deviceList = mService.getConnectedDevices();
+            while (!deviceList.isEmpty()) {
+                BluetoothDevice nextDevice = deviceList.remove(0);
+                CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice);
+                // Adds a new device into mDeviceManager if it does not exist
+                if (device == null) {
+                    Log.w(TAG, "HapClient profile found new device: " + nextDevice);
+                    device = mDeviceManager.addDevice(nextDevice);
+                }
+                device.onProfileStateChanged(
+                        HapClientProfile.this, BluetoothProfile.STATE_CONNECTED);
+                device.refresh();
+            }
+
+            mIsProfileReady = true;
+            mProfileManager.callServiceConnectedListeners();
+        }
+
+        @Override
+        public void onServiceDisconnected(int profile) {
+            mIsProfileReady = false;
+            mProfileManager.callServiceDisconnectedListeners();
+        }
+    }
+
+    HapClientProfile(Context context, CachedBluetoothDeviceManager deviceManager,
+            LocalBluetoothProfileManager profileManager) {
+        mDeviceManager = deviceManager;
+        mProfileManager = profileManager;
+        BluetoothManager bluetoothManager = context.getSystemService(BluetoothManager.class);
+        if (bluetoothManager != null) {
+            mBluetoothAdapter = bluetoothManager.getAdapter();
+            mBluetoothAdapter.getProfileProxy(context, new HapClientServiceListener(),
+                    BluetoothProfile.HAP_CLIENT);
+        } else {
+            mBluetoothAdapter = null;
+        }
+    }
+
+    /**
+     * Get hearing aid devices matching connection states{
+     * {@code BluetoothProfile.STATE_CONNECTED},
+     * {@code BluetoothProfile.STATE_CONNECTING},
+     * {@code BluetoothProfile.STATE_DISCONNECTING}}
+     *
+     * @return Matching device list
+     */
+    public List<BluetoothDevice> getConnectedDevices() {
+        return getDevicesByStates(new int[] {
+                BluetoothProfile.STATE_CONNECTED,
+                BluetoothProfile.STATE_CONNECTING,
+                BluetoothProfile.STATE_DISCONNECTING});
+    }
+
+    /**
+     * Get hearing aid devices matching connection states{
+     * {@code BluetoothProfile.STATE_DISCONNECTED},
+     * {@code BluetoothProfile.STATE_CONNECTED},
+     * {@code BluetoothProfile.STATE_CONNECTING},
+     * {@code BluetoothProfile.STATE_DISCONNECTING}}
+     *
+     * @return Matching device list
+     */
+    public List<BluetoothDevice> getConnectableDevices() {
+        return getDevicesByStates(new int[] {
+                BluetoothProfile.STATE_DISCONNECTED,
+                BluetoothProfile.STATE_CONNECTED,
+                BluetoothProfile.STATE_CONNECTING,
+                BluetoothProfile.STATE_DISCONNECTING});
+    }
+
+    private List<BluetoothDevice> getDevicesByStates(int[] states) {
+        if (mService == null) {
+            return new ArrayList<>(0);
+        }
+        return mService.getDevicesMatchingConnectionStates(states);
+    }
+
+    /**
+     * Gets the hearing aid type of the device.
+     *
+     * @param device is the device for which we want to get the hearing aid type
+     * @return hearing aid type
+     */
+    @HearingAidType
+    public int getHearingAidType(@NonNull BluetoothDevice device) {
+        if (mService == null) {
+            return HearingAidType.TYPE_INVALID;
+        }
+        return mService.getHearingAidType(device);
+    }
+
+    /**
+     * Gets if this device supports synchronized presets or not
+     *
+     * @param device is the device for which we want to know if supports synchronized presets
+     * @return {@code true} if the device supports synchronized presets
+     */
+    public boolean supportSynchronizedPresets(@NonNull BluetoothDevice device) {
+        if (mService == null) {
+            return false;
+        }
+        return mService.supportSynchronizedPresets(device);
+    }
+
+    /**
+     * Gets if this device supports independent presets or not
+     *
+     * @param device is the device for which we want to know if supports independent presets
+     * @return {@code true} if the device supports independent presets
+     */
+    public boolean supportIndependentPresets(@NonNull BluetoothDevice device) {
+        if (mService == null) {
+            return false;
+        }
+        return mService.supportIndependentPresets(device);
+    }
+
+    /**
+     * Gets if this device supports dynamic presets or not
+     *
+     * @param device is the device for which we want to know if supports dynamic presets
+     * @return {@code true} if the device supports dynamic presets
+     */
+    public boolean supportDynamicPresets(@NonNull BluetoothDevice device) {
+        if (mService == null) {
+            return false;
+        }
+        return mService.supportDynamicPresets(device);
+    }
+
+    /**
+     * Gets if this device supports writable presets or not
+     *
+     * @param device is the device for which we want to know if supports writable presets
+     * @return {@code true} if the device supports writable presets
+     */
+    public boolean supportWritablePresets(@NonNull BluetoothDevice device) {
+        if (mService == null) {
+            return false;
+        }
+        return mService.supportWritablePresets(device);
+    }
+
+    @Override
+    public boolean accessProfileEnabled() {
+        return false;
+    }
+
+    @Override
+    public boolean isAutoConnectable() {
+        return true;
+    }
+
+    @Override
+    public int getConnectionStatus(BluetoothDevice device) {
+        if (mService == null) {
+            return BluetoothProfile.STATE_DISCONNECTED;
+        }
+        return mService.getConnectionState(device);
+    }
+
+    @Override
+    public boolean isEnabled(BluetoothDevice device) {
+        if (mService == null || device == null) {
+            return false;
+        }
+        return mService.getConnectionPolicy(device) > CONNECTION_POLICY_FORBIDDEN;
+    }
+
+    @Override
+    public int getConnectionPolicy(BluetoothDevice device) {
+        if (mService == null || device == null) {
+            return CONNECTION_POLICY_FORBIDDEN;
+        }
+        return mService.getConnectionPolicy(device);
+    }
+
+    @Override
+    public boolean setEnabled(BluetoothDevice device, boolean enabled) {
+        boolean isEnabled = false;
+        if (mService == null || device == null) {
+            return false;
+        }
+        if (enabled) {
+            if (mService.getConnectionPolicy(device) < CONNECTION_POLICY_ALLOWED) {
+                isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_ALLOWED);
+            }
+        } else {
+            isEnabled = mService.setConnectionPolicy(device, CONNECTION_POLICY_FORBIDDEN);
+        }
+
+        return isEnabled;
+    }
+
+    @Override
+    public boolean isProfileReady() {
+        return mIsProfileReady;
+    }
+
+    @Override
+    public int getProfileId() {
+        return BluetoothProfile.HAP_CLIENT;
+    }
+
+    @Override
+    public int getOrdinal() {
+        return ORDINAL;
+    }
+
+    @Override
+    public int getNameResource(BluetoothDevice device) {
+        return R.string.bluetooth_profile_hearing_aid;
+    }
+
+    @Override
+    public int getSummaryResourceForDevice(BluetoothDevice device) {
+        int state = getConnectionStatus(device);
+        switch (state) {
+            case BluetoothProfile.STATE_DISCONNECTED:
+                return R.string.bluetooth_hearing_aid_profile_summary_use_for;
+
+            case BluetoothProfile.STATE_CONNECTED:
+                return R.string.bluetooth_hearing_aid_profile_summary_connected;
+
+            default:
+                return BluetoothUtils.getConnectionStateSummary(state);
+        }
+    }
+
+    @Override
+    public int getDrawableResource(BluetoothClass btClass) {
+        return com.android.internal.R.drawable.ic_bt_hearing_aid;
+    }
+
+    /**
+     * Gets the name of this class
+     *
+     * @return the name of this class
+     */
+    public String toString() {
+        return NAME;
+    }
+
+    protected void finalize() {
+        Log.d(TAG, "finalize()");
+        if (mService != null) {
+            try {
+                mBluetoothAdapter.closeProfileProxy(BluetoothProfile.HAP_CLIENT, mService);
+                mService = null;
+            } catch (Throwable t) {
+                Log.w(TAG, "Error cleaning up HAP Client proxy", t);
+            }
+        }
+    }
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java
index 03d9f2d..30d3820 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/DataServiceUtils.java
@@ -357,5 +357,12 @@
          * {@link SubscriptionManager#getDefaultSubscriptionId()}.
          */
         public static final String COLUMN_IS_DEFAULT_SUBSCRIPTION = "isDefaultSubscription";
+
+        /**
+         * The name of the active data subscription state column, see
+         * {@link SubscriptionManager#getActiveDataSubscriptionId()}.
+         */
+        public static final String COLUMN_IS_ACTIVE_DATA_SUBSCRIPTION =
+                "isActiveDataSubscriptionId";
     }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkDatabase.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkDatabase.java
index c1ee7ad..ca457b0 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkDatabase.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/MobileNetworkDatabase.java
@@ -20,6 +20,7 @@
 import android.util.Log;
 
 import java.util.List;
+import java.util.Objects;
 
 import androidx.lifecycle.LiveData;
 import androidx.room.Database;
@@ -39,17 +40,27 @@
 
     public abstract MobileNetworkInfoDao mMobileNetworkInfoDao();
 
+    private static MobileNetworkDatabase sInstance;
+    private static final Object sLOCK = new Object();
+
+
     /**
      * Create the MobileNetworkDatabase.
      *
      * @param context The context.
      * @return The MobileNetworkDatabase.
      */
-    public static MobileNetworkDatabase createDatabase(Context context) {
-        return Room.inMemoryDatabaseBuilder(context, MobileNetworkDatabase.class)
-                .fallbackToDestructiveMigration()
-                .enableMultiInstanceInvalidation()
-                .build();
+    public static MobileNetworkDatabase getInstance(Context context) {
+        synchronized (sLOCK) {
+            if (Objects.isNull(sInstance)) {
+                Log.d(TAG, "createDatabase.");
+                sInstance = Room.inMemoryDatabaseBuilder(context, MobileNetworkDatabase.class)
+                        .fallbackToDestructiveMigration()
+                        .enableMultiInstanceInvalidation()
+                        .build();
+            }
+        }
+        return sInstance;
     }
 
     /**
@@ -93,7 +104,7 @@
      * Query the subscription info by the subscription ID from the SubscriptionInfoEntity
      * table.
      */
-    public LiveData<SubscriptionInfoEntity> querySubInfoById(String id) {
+    public SubscriptionInfoEntity querySubInfoById(String id) {
         return mSubscriptionInfoDao().querySubInfoById(id);
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoDao.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoDao.java
index 4596637..e835125 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoDao.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoDao.java
@@ -37,7 +37,7 @@
 
     @Query("SELECT * FROM " + DataServiceUtils.SubscriptionInfoData.TABLE_NAME + " WHERE "
             + DataServiceUtils.SubscriptionInfoData.COLUMN_ID + " = :subId")
-    LiveData<SubscriptionInfoEntity> querySubInfoById(String subId);
+    SubscriptionInfoEntity querySubInfoById(String subId);
 
     @Query("SELECT * FROM " + DataServiceUtils.SubscriptionInfoData.TABLE_NAME + " WHERE "
             + DataServiceUtils.SubscriptionInfoData.COLUMN_IS_ACTIVE_SUBSCRIPTION_ID
diff --git a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoEntity.java b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoEntity.java
index 329bd9b..23566f7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoEntity.java
+++ b/packages/SettingsLib/src/com/android/settingslib/mobile/dataservice/SubscriptionInfoEntity.java
@@ -42,7 +42,7 @@
             boolean isUsableSubscription, boolean isActiveSubscriptionId,
             boolean isAvailableSubscription, boolean isDefaultVoiceSubscription,
             boolean isDefaultSmsSubscription, boolean isDefaultDataSubscription,
-            boolean isDefaultSubscription) {
+            boolean isDefaultSubscription, boolean isActiveDataSubscriptionId) {
         this.subId = subId;
         this.simSlotIndex = simSlotIndex;
         this.carrierId = carrierId;
@@ -72,6 +72,7 @@
         this.isDefaultSmsSubscription = isDefaultSmsSubscription;
         this.isDefaultDataSubscription = isDefaultDataSubscription;
         this.isDefaultSubscription = isDefaultSubscription;
+        this.isActiveDataSubscriptionId = isActiveDataSubscriptionId;
     }
 
     @PrimaryKey
@@ -165,6 +166,9 @@
     @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_IS_DEFAULT_SUBSCRIPTION)
     public boolean isDefaultSubscription;
 
+    @ColumnInfo(name = DataServiceUtils.SubscriptionInfoData.COLUMN_IS_ACTIVE_DATA_SUBSCRIPTION)
+    public boolean isActiveDataSubscriptionId;
+
     public int getSubId() {
         return Integer.valueOf(subId);
     }
@@ -213,6 +217,7 @@
         result = 31 * result + Boolean.hashCode(isDefaultSmsSubscription);
         result = 31 * result + Boolean.hashCode(isDefaultDataSubscription);
         result = 31 * result + Boolean.hashCode(isDefaultSubscription);
+        result = 31 * result + Boolean.hashCode(isActiveDataSubscriptionId);
         return result;
     }
 
@@ -254,7 +259,8 @@
                 && isDefaultVoiceSubscription == info.isDefaultVoiceSubscription
                 && isDefaultSmsSubscription == info.isDefaultSmsSubscription
                 && isDefaultDataSubscription == info.isDefaultDataSubscription
-                && isDefaultSubscription == info.isDefaultSubscription;
+                && isDefaultSubscription == info.isDefaultSubscription
+                && isActiveDataSubscriptionId == info.isActiveDataSubscriptionId;
     }
 
     public String toString() {
@@ -317,6 +323,8 @@
                 .append(isDefaultDataSubscription)
                 .append(", isDefaultSubscription = ")
                 .append(isDefaultSubscription)
+                .append(", isActiveDataSubscriptionId = ")
+                .append(isActiveDataSubscriptionId)
                 .append(")}");
         return builder.toString();
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/qrcode/OWNERS b/packages/SettingsLib/src/com/android/settingslib/qrcode/OWNERS
new file mode 100644
index 0000000..61c73fb
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/qrcode/OWNERS
@@ -0,0 +1,8 @@
+# Default reviewers for this and subdirectories.
+bonianchen@google.com
+changbetty@google.com
+goldmanj@google.com
+wengsu@google.com
+zoeychen@google.com
+
+# Emergency approvers in case the above are not available
diff --git a/packages/SettingsLib/src/com/android/settingslib/qrcode/QrDecorateView.java b/packages/SettingsLib/src/com/android/settingslib/qrcode/QrDecorateView.java
index 51cf59c..ac9cdac 100644
--- a/packages/SettingsLib/src/com/android/settingslib/qrcode/QrDecorateView.java
+++ b/packages/SettingsLib/src/com/android/settingslib/qrcode/QrDecorateView.java
@@ -34,16 +34,16 @@
     private static final float CORNER_LINE_LENGTH = 264f;   // 264dp
     private static final float CORNER_RADIUS = 16f;         // 16dp
 
-    final private int mCornerColor;
-    final private int mFocusedCornerColor;
-    final private int mBackgroundColor;
+    private final int mCornerColor;
+    private final int mFocusedCornerColor;
+    private final int mBackgroundColor;
 
-    final private Paint mStrokePaint;
-    final private Paint mTransparentPaint;
-    final private Paint mBackgroundPaint;
+    private final Paint mStrokePaint;
+    private final Paint mTransparentPaint;
+    private final Paint mBackgroundPaint;
 
-    final private float mRadius;
-    final private float mInnerRidus;
+    private final float mRadius;
+    private final float mInnerRadius;
 
     private Bitmap mMaskBitmap;
     private Canvas mMaskCanvas;
@@ -72,7 +72,7 @@
         mRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, CORNER_RADIUS,
                 getResources().getDisplayMetrics());
         // Inner radius needs to minus stroke width for keeping the width of border consistent.
-        mInnerRidus = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+        mInnerRadius = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
                 CORNER_RADIUS - CORNER_STROKE_WIDTH, getResources().getDisplayMetrics());
 
         mCornerColor = context.getResources().getColor(R.color.qr_corner_line_color);
@@ -95,7 +95,10 @@
     protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
         super.onLayout(changed, left, top, right, bottom);
 
-        if(mMaskBitmap == null) {
+        if (!isLaidOut()) {
+            return;
+        }
+        if (mMaskBitmap == null) {
             mMaskBitmap = Bitmap.createBitmap(getWidth(), getHeight(), Bitmap.Config.ARGB_8888);
             mMaskCanvas = new Canvas(mMaskBitmap);
         }
@@ -105,16 +108,18 @@
 
     @Override
     protected void onDraw(Canvas canvas) {
-        // Set frame line color.
-        mStrokePaint.setColor(mFocused ? mFocusedCornerColor : mCornerColor);
-        // Draw background color.
-        mMaskCanvas.drawColor(mBackgroundColor);
-        // Draw outer corner.
-        mMaskCanvas.drawRoundRect(mOuterFrame, mRadius, mRadius, mStrokePaint);
-        // Draw inner transparent corner.
-        mMaskCanvas.drawRoundRect(mInnerFrame, mInnerRidus, mInnerRidus, mTransparentPaint);
+        if (mMaskCanvas != null && mMaskBitmap != null) {
+            // Set frame line color.
+            mStrokePaint.setColor(mFocused ? mFocusedCornerColor : mCornerColor);
+            // Draw background color.
+            mMaskCanvas.drawColor(mBackgroundColor);
+            // Draw outer corner.
+            mMaskCanvas.drawRoundRect(mOuterFrame, mRadius, mRadius, mStrokePaint);
+            // Draw inner transparent corner.
+            mMaskCanvas.drawRoundRect(mInnerFrame, mInnerRadius, mInnerRadius, mTransparentPaint);
 
-        canvas.drawBitmap(mMaskBitmap, 0, 0, mBackgroundPaint);
+            canvas.drawBitmap(mMaskBitmap, 0, 0, mBackgroundPaint);
+        }
         super.onDraw(canvas);
     }
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HapClientProfileTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HapClientProfileTest.java
new file mode 100644
index 0000000..03a792a
--- /dev/null
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HapClientProfileTest.java
@@ -0,0 +1,209 @@
+/*
+ * Copyright (C) 2022 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.settingslib.bluetooth;
+
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
+import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.bluetooth.BluetoothDevice;
+import android.bluetooth.BluetoothHapClient;
+import android.bluetooth.BluetoothManager;
+import android.bluetooth.BluetoothProfile;
+import android.content.Context;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import com.android.settingslib.testutils.shadow.ShadowBluetoothAdapter;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import org.robolectric.shadow.api.Shadow;
+
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(RobolectricTestRunner.class)
+@Config(shadows = {ShadowBluetoothAdapter.class})
+public class HapClientProfileTest {
+
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+
+    @Mock
+    private CachedBluetoothDeviceManager mDeviceManager;
+    @Mock
+    private LocalBluetoothProfileManager mProfileManager;
+    @Mock
+    private BluetoothDevice mBluetoothDevice;
+    @Mock
+    private BluetoothHapClient mService;
+
+    private final Context mContext = ApplicationProvider.getApplicationContext();
+    private BluetoothProfile.ServiceListener mServiceListener;
+    private HapClientProfile mProfile;
+
+    @Before
+    public void setUp() {
+        mProfile = new HapClientProfile(mContext, mDeviceManager, mProfileManager);
+        final BluetoothManager bluetoothManager = mContext.getSystemService(BluetoothManager.class);
+        final ShadowBluetoothAdapter shadowBluetoothAdapter =
+                Shadow.extract(bluetoothManager.getAdapter());
+        mServiceListener = shadowBluetoothAdapter.getServiceListener();
+    }
+
+    @Test
+    public void onServiceConnected_isProfileReady() {
+        mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+
+        assertThat(mProfile.isProfileReady()).isTrue();
+        verify(mProfileManager).callServiceConnectedListeners();
+    }
+
+    @Test
+    public void onServiceDisconnected_isProfileNotReady() {
+        mServiceListener.onServiceDisconnected(BluetoothProfile.HAP_CLIENT);
+
+        assertThat(mProfile.isProfileReady()).isFalse();
+        verify(mProfileManager).callServiceDisconnectedListeners();
+    }
+
+    @Test
+    public void getConnectionStatus_returnCorrectConnectionState() {
+        mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+        when(mService.getConnectionState(mBluetoothDevice))
+                .thenReturn(BluetoothProfile.STATE_CONNECTED);
+
+        assertThat(mProfile.getConnectionStatus(mBluetoothDevice))
+                .isEqualTo(BluetoothProfile.STATE_CONNECTED);
+    }
+
+    @Test
+    public void isEnabled_connectionPolicyAllowed_returnTrue() {
+        mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+        when(mService.getConnectionPolicy(mBluetoothDevice)).thenReturn(CONNECTION_POLICY_ALLOWED);
+
+        assertThat(mProfile.isEnabled(mBluetoothDevice)).isTrue();
+    }
+
+    @Test
+    public void isEnabled_connectionPolicyForbidden_returnFalse() {
+        mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+        when(mService.getConnectionPolicy(mBluetoothDevice))
+                .thenReturn(CONNECTION_POLICY_FORBIDDEN);
+
+        assertThat(mProfile.isEnabled(mBluetoothDevice)).isFalse();
+    }
+
+    @Test
+    public void getConnectionPolicy_returnCorrectConnectionPolicy() {
+        mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+        when(mService.getConnectionPolicy(mBluetoothDevice)).thenReturn(CONNECTION_POLICY_ALLOWED);
+
+        assertThat(mProfile.getConnectionPolicy(mBluetoothDevice))
+                .isEqualTo(CONNECTION_POLICY_ALLOWED);
+    }
+
+    @Test
+    public void setEnabled_connectionPolicyAllowed_setConnectionPolicyAllowed_returnFalse() {
+        mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+        when(mService.getConnectionPolicy(mBluetoothDevice)).thenReturn(CONNECTION_POLICY_ALLOWED);
+        when(mService.setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED))
+                .thenReturn(true);
+
+        assertThat(mProfile.setEnabled(mBluetoothDevice, true)).isFalse();
+    }
+
+    @Test
+    public void setEnabled_connectionPolicyForbidden_setConnectionPolicyAllowed_returnTrue() {
+        mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+        when(mService.getConnectionPolicy(mBluetoothDevice))
+                .thenReturn(CONNECTION_POLICY_FORBIDDEN);
+        when(mService.setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_ALLOWED))
+                .thenReturn(true);
+
+        assertThat(mProfile.setEnabled(mBluetoothDevice, true)).isTrue();
+    }
+
+    @Test
+    public void setEnabled_connectionPolicyAllowed_setConnectionPolicyForbidden_returnTrue() {
+        mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+        when(mService.getConnectionPolicy(mBluetoothDevice)).thenReturn(CONNECTION_POLICY_ALLOWED);
+        when(mService.setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN))
+                .thenReturn(true);
+
+        assertThat(mProfile.setEnabled(mBluetoothDevice, false)).isTrue();
+    }
+
+    @Test
+    public void setEnabled_connectionPolicyForbidden_setConnectionPolicyForbidden_returnTrue() {
+        mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+        when(mService.getConnectionPolicy(mBluetoothDevice))
+                .thenReturn(CONNECTION_POLICY_FORBIDDEN);
+        when(mService.setConnectionPolicy(mBluetoothDevice, CONNECTION_POLICY_FORBIDDEN))
+                .thenReturn(true);
+
+        assertThat(mProfile.setEnabled(mBluetoothDevice, false)).isTrue();
+    }
+
+    @Test
+    public void getConnectedDevices_returnCorrectList() {
+        mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+        int[] connectedStates = new int[] {
+                BluetoothProfile.STATE_CONNECTED,
+                BluetoothProfile.STATE_CONNECTING,
+                BluetoothProfile.STATE_DISCONNECTING};
+        List<BluetoothDevice> connectedList = Arrays.asList(
+                mBluetoothDevice,
+                mBluetoothDevice,
+                mBluetoothDevice);
+        when(mService.getDevicesMatchingConnectionStates(connectedStates))
+                .thenReturn(connectedList);
+
+        assertThat(mProfile.getConnectedDevices().size()).isEqualTo(connectedList.size());
+    }
+
+    @Test
+    public void getConnectableDevices_returnCorrectList() {
+        mServiceListener.onServiceConnected(BluetoothProfile.HAP_CLIENT, mService);
+        int[] connectableStates = new int[] {
+                BluetoothProfile.STATE_DISCONNECTED,
+                BluetoothProfile.STATE_CONNECTED,
+                BluetoothProfile.STATE_CONNECTING,
+                BluetoothProfile.STATE_DISCONNECTING};
+        List<BluetoothDevice> connectableList = Arrays.asList(
+                mBluetoothDevice,
+                mBluetoothDevice,
+                mBluetoothDevice,
+                mBluetoothDevice);
+        when(mService.getDevicesMatchingConnectionStates(connectableStates))
+                .thenReturn(connectableList);
+
+        assertThat(mProfile.getConnectableDevices().size()).isEqualTo(connectableList.size());
+    }
+}
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index def7ddc..98af15a 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -122,6 +122,7 @@
         Settings.Secure.FINGERPRINT_SIDE_FPS_BP_POWER_WINDOW,
         Settings.Secure.FINGERPRINT_SIDE_FPS_ENROLL_TAP_WINDOW,
         Settings.Secure.FINGERPRINT_SIDE_FPS_AUTH_DOWNTIME,
+        Settings.Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED,
         Settings.Secure.ACTIVE_UNLOCK_ON_WAKE,
         Settings.Secure.ACTIVE_UNLOCK_ON_UNLOCK_INTENT,
         Settings.Secure.ACTIVE_UNLOCK_ON_BIOMETRIC_FAIL,
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index cde4bc4..80af69c 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -177,6 +177,7 @@
         VALIDATORS.put(Secure.FINGERPRINT_SIDE_FPS_ENROLL_TAP_WINDOW,
                 NON_NEGATIVE_INTEGER_VALIDATOR);
         VALIDATORS.put(Secure.FINGERPRINT_SIDE_FPS_AUTH_DOWNTIME, NON_NEGATIVE_INTEGER_VALIDATOR);
+        VALIDATORS.put(Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.SHOW_MEDIA_WHEN_BYPASSING, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.FACE_UNLOCK_APP_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.FACE_UNLOCK_ALWAYS_REQUIRE_CONFIRMATION, BOOLEAN_VALIDATOR);
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 753e110..7a4a93f 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -125,6 +125,7 @@
         "jsr330",
         "lottie",
         "LowLightDreamLib",
+        "motion_tool_lib",
     ],
     manifest: "AndroidManifest.xml",
 
@@ -230,6 +231,7 @@
         "jsr330",
         "WindowManager-Shell",
         "LowLightDreamLib",
+        "motion_tool_lib",
     ],
     libs: [
         "android.test.runner",
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 4267ba2..07e37d3 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -557,6 +557,16 @@
                   android:showForAllUsers="true">
         </activity>
 
+        <!-- started from SensoryPrivacyService -->
+        <activity android:name=".sensorprivacy.television.TvSensorPrivacyChangedActivity"
+            android:exported="true"
+            android:launchMode="singleTop"
+            android:permission="android.permission.MANAGE_SENSOR_PRIVACY"
+            android:theme="@style/BottomSheet"
+            android:finishOnCloseSystemDialogs="true"
+            android:showForAllUsers="true">
+        </activity>
+
 
         <!-- started from UsbDeviceSettingsManager -->
         <activity android:name=".usb.UsbAccessoryUriActivity"
diff --git a/packages/SystemUI/ktfmt_includes.txt b/packages/SystemUI/ktfmt_includes.txt
index 0f037e4..06ea381 100644
--- a/packages/SystemUI/ktfmt_includes.txt
+++ b/packages/SystemUI/ktfmt_includes.txt
@@ -27,8 +27,6 @@
 -packages/SystemUI/shared/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
 -packages/SystemUI/shared/src/com/android/systemui/shared/clocks/ClockRegistry.kt
 -packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
--packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionDarkness.kt
--packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSamplingInstance.kt
 -packages/SystemUI/shared/src/com/android/systemui/shared/rotation/FloatingRotationButtonPositionCalculator.kt
 -packages/SystemUI/shared/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerManager.kt
 -packages/SystemUI/shared/src/com/android/systemui/shared/system/smartspace/SmartspaceState.kt
@@ -683,8 +681,6 @@
 -packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldConstantTranslateAnimatorTest.kt
 -packages/SystemUI/tests/src/com/android/systemui/shared/animation/UnfoldMoveFromCenterAnimatorTest.kt
 -packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
--packages/SystemUI/tests/src/com/android/systemui/shared/navigationbar/RegionSamplingHelperTest.kt
--packages/SystemUI/tests/src/com/android/systemui/shared/regionsampling/RegionSamplingInstanceTest.kt
 -packages/SystemUI/tests/src/com/android/systemui/shared/rotation/RotationButtonControllerTest.kt
 -packages/SystemUI/tests/src/com/android/systemui/shared/system/UncaughtExceptionPreHandlerTest.kt
 -packages/SystemUI/tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
index d64587d..c297149 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_clock_switch.xml
@@ -31,7 +31,8 @@
         android:layout_height="wrap_content"
         android:layout_alignParentStart="true"
         android:layout_alignParentTop="true"
-        android:paddingStart="@dimen/clock_padding_start" />
+        android:paddingStart="@dimen/clock_padding_start"
+        android:visibility="invisible" />
     <FrameLayout
         android:id="@+id/lockscreen_clock_view_large"
         android:layout_width="match_parent"
diff --git a/packages/SystemUI/res-keyguard/values-af/strings.xml b/packages/SystemUI/res-keyguard/values-af/strings.xml
index d5552f6..1ff549e 100644
--- a/packages/SystemUI/res-keyguard/values-af/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-af/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Patroon word vereis nadat toestel herbegin het"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN word vereis nadat toestel herbegin het"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Wagwoord word vereis nadat toestel herbegin het"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Gebruik eerder ’n patroon vir bykomende sekuriteit"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Gebruik eerder ’n PIN vir bykomende sekuriteit"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Gebruik eerder ’n wagwoord vir bykomende sekuriteit"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Toestel is deur administrateur gesluit"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Toestel is handmatig gesluit"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Nie herken nie"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Verstek"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Borrel"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analoog"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Ontsluit jou toestel om voort te gaan"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-am/strings.xml b/packages/SystemUI/res-keyguard/values-am/strings.xml
index 533e5a2..f61c8cf 100644
--- a/packages/SystemUI/res-keyguard/values-am/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-am/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"መሣሪያ ዳግም ከጀመረ በኋላ ሥርዓተ ጥለት ያስፈልጋል"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"መሣሪያ ዳግም ከተነሳ በኋላ ፒን ያስፈልጋል"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"መሣሪያ ዳግም ከጀመረ በኋላ የይለፍ ቃል ያስፈልጋል"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"ለተጨማሪ ደህንነት በምትኩ ስርዓተ ጥለት ይጠቀሙ"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"ለተጨማሪ ደህንነት በምትኩ ፒን ይጠቀሙ"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"ለተጨማሪ ደህንነት በምትኩ የይለፍ ቃል ይጠቀሙ"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"መሣሪያ በአስተዳዳሪ ተቆልፏል"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"መሣሪያ በተጠቃሚው ራሱ ተቆልፏል"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"አልታወቀም"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"ነባሪ"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"አረፋ"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"አናሎግ"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"ለመቀጠል መሣሪያዎን ይክፈቱ"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-ar/strings.xml b/packages/SystemUI/res-keyguard/values-ar/strings.xml
index 81ce7d3..f3256ba 100644
--- a/packages/SystemUI/res-keyguard/values-ar/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ar/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"يجب رسم النقش بعد إعادة تشغيل الجهاز"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"يجب إدخال رقم التعريف الشخصي بعد إعادة تشغيل الجهاز"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"يجب إدخال كلمة المرور بعد إعادة تشغيل الجهاز"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"لمزيد من الأمان، استخدِم النقش بدلاً من ذلك."</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"لمزيد من الأمان، أدخِل رقم التعريف الشخصي بدلاً من ذلك."</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"لمزيد من الأمان، أدخِل كلمة المرور بدلاً من ذلك."</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"اختار المشرف قفل الجهاز"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"تم حظر الجهاز يدويًا"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"لم يتم التعرّف عليه."</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"تلقائي"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"فقاعة"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"ساعة تقليدية"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"يجب فتح قفل الجهاز للمتابعة"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-as/strings.xml b/packages/SystemUI/res-keyguard/values-as/strings.xml
index 443f666..f9dc46f 100644
--- a/packages/SystemUI/res-keyguard/values-as/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-as/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ডিভাইচ ৰিষ্টাৰ্ট হোৱাৰ পাছত আৰ্হি দিয়াটো বাধ্যতামূলক"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ডিভাইচ ৰিষ্টাৰ্ট হোৱাৰ পাছত পিন দিয়াটো বাধ্যতামূলক"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ডিভাইচ ৰিষ্টাৰ্ট হোৱাৰ পাছত পাছৱৰ্ড দিয়াটো বাধ্যতামূলক"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"অতিৰিক্ত সুৰক্ষাৰ বাবে, ইয়াৰ পৰিৱৰ্তে আৰ্হি ব্যৱহাৰ কৰক"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"অতিৰিক্ত সুৰক্ষাৰ বাবে, ইয়াৰ পৰিৱৰ্তে পিন ব্যৱহাৰ কৰক"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"অতিৰিক্ত সুৰক্ষাৰ বাবে, ইয়াৰ পৰিৱৰ্তে পাছৱৰ্ড ব্যৱহাৰ কৰক"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"প্ৰশাসকে ডিভাইচ লক কৰি ৰাখিছে"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"ডিভাইচটো মেনুৱেলভাৱে লক কৰা হৈছিল"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"চিনাক্ত কৰিব পৰা নাই"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"ডিফ’ল্ট"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"বাবল"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"এনাল’গ"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"অব্যাহত ৰাখিবলৈ আপোনাৰ ডিভাইচটো আনলক কৰক"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-az/strings.xml b/packages/SystemUI/res-keyguard/values-az/strings.xml
index e125697..65c1c93 100644
--- a/packages/SystemUI/res-keyguard/values-az/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-az/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Cihaz yenidən başladıqdan sonra model tələb olunur"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Cihaz yeniden başladıqdan sonra PIN tələb olunur"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Cihaz yeniden başladıqdan sonra parol tələb olunur"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Əlavə təhlükəsizlik üçün modeldən istifadə edin"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Əlavə təhlükəsizlik üçün PIN istifadə edin"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Əlavə təhlükəsizlik üçün paroldan istifadə edin"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Cihaz admin tərəfindən kilidlənib"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Cihaz əl ilə kilidləndi"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Tanınmır"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Defolt"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Qabarcıq"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analoq"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Davam etmək üçün cihazınızın kilidini açın"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml b/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml
index f0d1ef2..cf363df 100644
--- a/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-b+sr+Latn/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Treba da unesete šablon kada se uređaj ponovo pokrene"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Treba da unesete PIN kada se uređaj ponovo pokrene"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Treba da unesete lozinku kada se uređaj ponovo pokrene"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Za dodatnu bezbednost koristite šablon"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Za dodatnu bezbednost koristite PIN"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Za dodatnu bezbednost koristite lozinku"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Administrator je zaključao uređaj"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Uređaj je ručno zaključan"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Nije prepoznat"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Podrazumevani"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Mehurići"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analogni"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Otključajte uređaj da biste nastavili"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-be/strings.xml b/packages/SystemUI/res-keyguard/values-be/strings.xml
index e1af3ece..c2dedf30 100644
--- a/packages/SystemUI/res-keyguard/values-be/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-be/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Пасля перазапуску прылады патрабуецца ўзор"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Пасля перазапуску прылады патрабуецца PIN-код"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Пасля перазапуску прылады патрабуецца пароль"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"У мэтах дадатковай бяспекі скарыстайце ўзор разблакіроўкі"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"У мэтах дадатковай бяспекі скарыстайце PIN-код"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"У мэтах дадатковай бяспекі скарыстайце пароль"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Прылада заблакіравана адміністратарам"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Прылада была заблакіравана ўручную"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Не распазнана"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Стандартны"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Бурбалкі"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Са стрэлкамі"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Каб працягнуць, разблакіруйце прыладу"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-bg/strings.xml b/packages/SystemUI/res-keyguard/values-bg/strings.xml
index 0b4417a..546a645 100644
--- a/packages/SystemUI/res-keyguard/values-bg/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-bg/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"След рестартиране на устройството се изисква фигура"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"След рестартиране на устройството се изисква ПИН код"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"След рестартиране на устройството се изисква парола"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"За допълнителна сигурност използвайте фигура вместо това"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"За допълнителна сигурност използвайте ПИН код вместо това"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"За допълнителна сигурност използвайте парола вместо това"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Устройството е заключено от администратора"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Устройството бе заключено ръчно"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Не е разпознато"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Стандартен"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Балонен"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Аналогов"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Отключете устройството си, за да продължите"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-bn/strings.xml b/packages/SystemUI/res-keyguard/values-bn/strings.xml
index 4851579..7b3df35 100644
--- a/packages/SystemUI/res-keyguard/values-bn/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-bn/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ডিভাইসটি পুনরায় চালু হওয়ার পর প্যাটার্নের প্রয়োজন হবে"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ডিভাইসটি পুনরায় চালু হওয়ার পর পিন প্রয়োজন হবে"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ডিভাইসটি পুনরায় চালু হওয়ার পর পাসওয়ার্ডের প্রয়োজন হবে"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"অতিরিক্ত সুরক্ষার জন্য, এর বদলে প্যাটার্ন ব্যবহার করুন"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"অতিরিক্ত সুরক্ষার জন্য, এর বদলে পিন ব্যবহার করুন"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"অতিরিক্ত সুরক্ষার জন্য, এর বদলে পাসওয়ার্ড ব্যবহার করুন"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"প্রশাসক ডিভাইসটি লক করেছেন"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"ডিভাইসটিকে ম্যানুয়ালি লক করা হয়েছে"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"শনাক্ত করা যায়নি"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"ডিফল্ট"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"বাবল"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"অ্যানালগ"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"চালিয়ে যেতে আপনার ডিভাইস আনলক করুন"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-bs/strings.xml b/packages/SystemUI/res-keyguard/values-bs/strings.xml
index 4705b4d9..bb9e690 100644
--- a/packages/SystemUI/res-keyguard/values-bs/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-bs/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Potreban je uzorak nakon što se uređaj ponovo pokrene"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Potreban je PIN nakon što se uređaj ponovo pokrene"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Potrebna je lozinka nakon što se uređaj ponovo pokrene"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Radi dodatne zaštite, umjesto toga koristite uzorak"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Radi dodatne zaštite, umjesto toga koristite PIN"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Radi dodatne zašitite, umjesto toga koristite lozinku"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Uređaj je zaključao administrator"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Uređaj je ručno zaključan"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Nije prepoznato"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Zadano"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Mjehurići"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analogni"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Otključajte uređaj da nastavite"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-ca/strings.xml b/packages/SystemUI/res-keyguard/values-ca/strings.xml
index 284eaeb..1c81c60 100644
--- a/packages/SystemUI/res-keyguard/values-ca/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ca/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Cal introduir el patró quan es reinicia el dispositiu"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Cal introduir el PIN quan es reinicia el dispositiu"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Cal introduir la contrasenya quan es reinicia el dispositiu"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Per a més seguretat, utilitza el patró"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Per a més seguretat, utilitza el PIN"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Per a més seguretat, utilitza la contrasenya"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"L\'administrador ha bloquejat el dispositiu"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"El dispositiu s\'ha bloquejat manualment"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"No s\'ha reconegut"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Predeterminada"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Bombolla"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analògica"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Desbloqueja el dispositiu per continuar"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-cs/strings.xml b/packages/SystemUI/res-keyguard/values-cs/strings.xml
index 6b4f607..9a6178c 100644
--- a/packages/SystemUI/res-keyguard/values-cs/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-cs/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Po restartování zařízení je vyžadováno gesto"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Po restartování zařízení je vyžadován kód PIN"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Po restartování zařízení je vyžadováno heslo"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Z bezpečnostních důvodů raději použijte gesto"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Z bezpečnostních důvodů raději použijte PIN"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Z bezpečnostních důvodů raději použijte heslo"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Zařízení je uzamknuto administrátorem"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Zařízení bylo ručně uzamčeno"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Nerozpoznáno"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Výchozí"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Bublina"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analogové"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Pokud chcete pokračovat, odemkněte zařízení"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-da/strings.xml b/packages/SystemUI/res-keyguard/values-da/strings.xml
index 85238df..aac1b83 100644
--- a/packages/SystemUI/res-keyguard/values-da/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-da/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Du skal angive et mønster, når du har genstartet enheden"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Der skal angives en pinkode efter genstart af enheden"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Der skal angives en adgangskode efter genstart af enheden"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Øg sikkerheden ved at bruge dit oplåsningsmønter i stedet"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Øg sikkerheden ved at bruge din pinkode i stedet"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Øg sikkerheden ved at bruge din adgangskode i stedet"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Enheden er blevet låst af administratoren"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Enheden blev låst manuelt"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Ikke genkendt"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Standard"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Boble"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Lås din enhed op for at fortsætte"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-de/strings.xml b/packages/SystemUI/res-keyguard/values-de/strings.xml
index 18befed..5a340ff 100644
--- a/packages/SystemUI/res-keyguard/values-de/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-de/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Nach dem Neustart des Geräts ist die Eingabe des Musters erforderlich"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Nach dem Neustart des Geräts ist die Eingabe der PIN erforderlich"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Nach dem Neustart des Geräts ist die Eingabe des Passworts erforderlich"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Verwende für mehr Sicherheit stattdessen dein Muster"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Verwende für mehr Sicherheit stattdessen deine PIN"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Verwende für mehr Sicherheit stattdessen dein Passwort"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Gerät vom Administrator gesperrt"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Gerät manuell gesperrt"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Nicht erkannt"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Standard"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Bubble"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Gerät entsperren, um fortzufahren"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-el/strings.xml b/packages/SystemUI/res-keyguard/values-el/strings.xml
index 65b84486..973139f 100644
--- a/packages/SystemUI/res-keyguard/values-el/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-el/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Απαιτείται μοτίβο μετά από την επανεκκίνηση της συσκευής"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Απαιτείται PIN μετά από την επανεκκίνηση της συσκευής"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Απαιτείται κωδικός πρόσβασης μετά από την επανεκκίνηση της συσκευής"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Για πρόσθετη ασφάλεια, χρησιμοποιήστε εναλλακτικά μοτίβο"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Για πρόσθετη ασφάλεια, χρησιμοποιήστε εναλλακτικά PIN"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Για πρόσθετη ασφάλεια, χρησιμοποιήστε εναλλακτικά κωδικό πρόσβασης"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Η συσκευή κλειδώθηκε από τον διαχειριστή"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Η συσκευή κλειδώθηκε με μη αυτόματο τρόπο"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Δεν αναγνωρίστηκε"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Προεπιλογή"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Συννεφάκι"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Αναλογικό"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Ξεκλειδώστε τη συσκευή σας για να συνεχίσετε"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-en-rAU/strings.xml b/packages/SystemUI/res-keyguard/values-en-rAU/strings.xml
index 588f1b5..41eaa389 100644
--- a/packages/SystemUI/res-keyguard/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rAU/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Pattern required after device restarts"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN required after device restarts"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Password required after device restarts"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"For additional security, use pattern instead"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"For additional security, use PIN instead"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"For additional security, use password instead"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Device locked by admin"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Device was locked manually"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Not recognised"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Default"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Bubble"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analogue"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Unlock your device to continue"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml b/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml
index 08fc8d6..c8ba237 100644
--- a/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rCA/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Pattern required after device restarts"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN required after device restarts"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Password required after device restarts"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"For additional security, use pattern instead"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"For additional security, use PIN instead"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"For additional security, use password instead"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Device locked by admin"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Device was locked manually"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Not recognised"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Default"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Bubble"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analogue"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Unlock your device to continue"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-en-rGB/strings.xml b/packages/SystemUI/res-keyguard/values-en-rGB/strings.xml
index 588f1b5..41eaa389 100644
--- a/packages/SystemUI/res-keyguard/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rGB/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Pattern required after device restarts"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN required after device restarts"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Password required after device restarts"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"For additional security, use pattern instead"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"For additional security, use PIN instead"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"For additional security, use password instead"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Device locked by admin"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Device was locked manually"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Not recognised"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Default"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Bubble"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analogue"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Unlock your device to continue"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml b/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml
index 588f1b5..41eaa389 100644
--- a/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-en-rIN/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Pattern required after device restarts"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN required after device restarts"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Password required after device restarts"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"For additional security, use pattern instead"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"For additional security, use PIN instead"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"For additional security, use password instead"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Device locked by admin"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Device was locked manually"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Not recognised"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Default"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Bubble"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analogue"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Unlock your device to continue"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-es-rUS/strings.xml b/packages/SystemUI/res-keyguard/values-es-rUS/strings.xml
index c71a678..6314d90 100644
--- a/packages/SystemUI/res-keyguard/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-es-rUS/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Se requiere el patrón después de reiniciar el dispositivo"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Se requiere el PIN después de reiniciar el dispositivo"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Se requiere la contraseña después de reiniciar el dispositivo"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Para seguridad adicional, usa un patrón"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Para seguridad adicional, usa un PIN"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Para seguridad adicional, usa una contraseña"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Dispositivo bloqueado por el administrador"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"El dispositivo se bloqueó de forma manual"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"No se reconoció"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Predeterminado"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Burbuja"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analógico"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Desbloquea tu dispositivo para continuar"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-es/strings.xml b/packages/SystemUI/res-keyguard/values-es/strings.xml
index c6ee698..5aecf84 100644
--- a/packages/SystemUI/res-keyguard/values-es/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-es/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Debes introducir el patrón después de reiniciar el dispositivo"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Debes introducir el PIN después de reiniciar el dispositivo"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Debes introducir la contraseña después de reiniciar el dispositivo"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Para mayor seguridad, usa el patrón"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Para mayor seguridad, usa el PIN"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Para mayor seguridad, usa la contraseña"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Dispositivo bloqueado por el administrador"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"El dispositivo se ha bloqueado manualmente"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"No se reconoce"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Predeterminado"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Burbuja"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analógico"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Desbloquea tu dispositivo para continuar"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-et/strings.xml b/packages/SystemUI/res-keyguard/values-et/strings.xml
index 071ede8..9306ff6 100644
--- a/packages/SystemUI/res-keyguard/values-et/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-et/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Pärast seadme taaskäivitamist tuleb sisestada muster"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Pärast seadme taaskäivitamist tuleb sisestada PIN-kood"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Pärast seadme taaskäivitamist tuleb sisestada parool"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Kasutage tugevama turvalisuse huvides hoopis mustrit"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Kasutage tugevama turvalisuse huvides hoopis PIN-koodi"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Kasutage tugevama turvalisuse huvides hoopis parooli"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Administraator lukustas seadme"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Seade lukustati käsitsi"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Ei tuvastatud"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Vaikenumbrilaud"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Mull"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analoog"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Jätkamiseks avage oma seade"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-eu/strings.xml b/packages/SystemUI/res-keyguard/values-eu/strings.xml
index 9b8e65b..4ebe0f0 100644
--- a/packages/SystemUI/res-keyguard/values-eu/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-eu/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Eredua marraztu beharko duzu gailua berrabiarazten denean"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PINa idatzi beharko duzu gailua berrabiarazten denean"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Pasahitza idatzi beharko duzu gailua berrabiarazten denean"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Babestuago egoteko, erabili eredua"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Babestuago egoteko, erabili PINa"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Babestuago egoteko, erabili pasahitza"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Administratzaileak blokeatu egin du gailua"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Eskuz blokeatu da gailua"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Ez da ezagutu"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Lehenetsia"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Puxikak"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analogikoa"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Aurrera egiteko, desblokeatu gailua"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-fa/strings.xml b/packages/SystemUI/res-keyguard/values-fa/strings.xml
index 3583f1e..e9a2e87 100644
--- a/packages/SystemUI/res-keyguard/values-fa/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-fa/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"بعد از بازنشانی دستگاه باید الگو وارد شود"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"بعد از بازنشانی دستگاه باید پین وارد شود"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"بعد از بازنشانی دستگاه باید گذرواژه وارد شود"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"برای امنیت بیشتر، به‌جای آن از الگو استفاده کنید"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"برای امنیت بیشتر، به‌جای آن از پین استفاده کنید"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"برای امنیت بیشتر، به‌جای آن از گذرواژه استفاده کنید"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"دستگاه توسط سرپرست سیستم قفل شده است"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"دستگاه به‌صورت دستی قفل شده است"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"شناسایی نشد"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"پیش‌فرض"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"حباب"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"آنالوگ"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"برای ادامه، قفل دستگاهتان را باز کنید"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-fi/strings.xml b/packages/SystemUI/res-keyguard/values-fi/strings.xml
index a0ac6df..e80869a 100644
--- a/packages/SystemUI/res-keyguard/values-fi/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-fi/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Kuvio vaaditaan laitteen uudelleenkäynnistyksen jälkeen."</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN-koodi vaaditaan laitteen uudelleenkäynnistyksen jälkeen."</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Salasana vaaditaan laitteen uudelleenkäynnistyksen jälkeen."</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Lisäsuojaa saat, kun käytät sen sijaan kuviota"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Lisäsuojaa saat, kun käytät sen sijaan PIN-koodia"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Lisäsuojaa saat, kun käytät sen sijaan salasanaa"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Järjestelmänvalvoja lukitsi laitteen."</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Laite lukittiin manuaalisesti"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Ei tunnistettu"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Oletus"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Kupla"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analoginen"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Jatka avaamalla laitteen lukitus"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-fr/strings.xml b/packages/SystemUI/res-keyguard/values-fr/strings.xml
index ec00ba3..92d0617 100644
--- a/packages/SystemUI/res-keyguard/values-fr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-fr/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Veuillez dessiner le schéma après le redémarrage de l\'appareil"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Veuillez saisir le code après le redémarrage de l\'appareil"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Veuillez saisir le mot de passe après le redémarrage de l\'appareil"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Pour plus de sécurité, utilisez plutôt un schéma"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Pour plus de sécurité, utilisez plutôt un code"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Pour plus de sécurité, utilisez plutôt un mot de passe"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Appareil verrouillé par l\'administrateur"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Appareil verrouillé manuellement"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Non reconnu"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Par défaut"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Bulle"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analogique"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Déverrouillez votre appareil pour continuer"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-gl/strings.xml b/packages/SystemUI/res-keyguard/values-gl/strings.xml
index a3f8e86..776e90a 100644
--- a/packages/SystemUI/res-keyguard/values-gl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-gl/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"É necesario o padrón despois do reinicio do dispositivo"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"É necesario o PIN despois do reinicio do dispositivo"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"É necesario o contrasinal despois do reinicio do dispositivo"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Utiliza un padrón para obter maior seguranza"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Utiliza un PIN para obter maior seguranza"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Utiliza un contrasinal para obter maior seguranza"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"O administrador bloqueou o dispositivo"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"O dispositivo bloqueouse manualmente"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Non se recoñeceu"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Predeterminado"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Burbulla"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analóxico"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Desbloquea o dispositivo para continuar"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-gu/strings.xml b/packages/SystemUI/res-keyguard/values-gu/strings.xml
index c97fe01..a8b9a3a 100644
--- a/packages/SystemUI/res-keyguard/values-gu/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-gu/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ઉપકરણનો પુનઃપ્રારંભ થાય તે પછી પૅટર્ન જરૂરી છે"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ઉપકરણનો પુનઃપ્રારંભ થાય તે પછી પિન જરૂરી છે"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ઉપકરણનો પુનઃપ્રારંભ થાય તે પછી પાસવર્ડ જરૂરી છે"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"વધારાની સુરક્ષા માટે, તેના બદલે પૅટર્નનો ઉપયોગ કરો"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"વધારાની સુરક્ષા માટે, તેના બદલે પિનનો ઉપયોગ કરો"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"વધારાની સુરક્ષા માટે, તેના બદલે પાસવર્ડનો ઉપયોગ કરો"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"વ્યવસ્થાપકે ઉપકરણ લૉક કરેલું છે"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"ઉપકરણ મેન્યુઅલી લૉક કર્યું હતું"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"ઓળખાયેલ નથી"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"ડિફૉલ્ટ"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"બબલ"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"એનાલોગ"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"ચાલુ રાખવા માટે તમારા ડિવાઇસને અનલૉક કરો"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-hi/strings.xml b/packages/SystemUI/res-keyguard/values-hi/strings.xml
index 1283004..47560dd 100644
--- a/packages/SystemUI/res-keyguard/values-hi/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-hi/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"डिवाइस फिर से चालू होने के बाद पैटर्न ज़रूरी है"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"डिवाइस फिर से चालू होने के बाद पिन ज़रूरी है"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"डिवाइस फिर से चालू होने के बाद पासवर्ड ज़रूरी है"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"ज़्यादा सुरक्षा के लिए, इसके बजाय पैटर्न का इस्तेमाल करें"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"ज़्यादा सुरक्षा के लिए, इसके बजाय पिन का इस्तेमाल करें"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"ज़्यादा सुरक्षा के लिए, इसके बजाय पासवर्ड का इस्तेमाल करें"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"व्यवस्थापक ने डिवाइस को लॉक किया है"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"डिवाइस को मैन्युअल रूप से लॉक किया गया था"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"पहचान नहीं हो पाई"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"डिफ़ॉल्ट"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"बबल"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"एनालॉग"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"जारी रखने के लिए डिवाइस अनलॉक करें"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-hr/strings.xml b/packages/SystemUI/res-keyguard/values-hr/strings.xml
index 7a14a80..efd1cbb 100644
--- a/packages/SystemUI/res-keyguard/values-hr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-hr/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Nakon ponovnog pokretanja uređaja morate unijeti uzorak"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Nakon ponovnog pokretanja uređaja morate unijeti PIN"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Nakon ponovnog pokretanja uređaja morate unijeti zaporku"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Za dodatnu sigurnost upotrijebite uzorak"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Za dodatnu sigurnost upotrijebite PIN"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Za dodatnu sigurnost upotrijebite zaporku"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Administrator je zaključao uređaj"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Uređaj je ručno zaključan"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Nije prepoznato"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Zadano"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Mjehurić"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analogni"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Otključajte uređaj da biste nastavili"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-hu/strings.xml b/packages/SystemUI/res-keyguard/values-hu/strings.xml
index a4fbf53..0421ff8 100644
--- a/packages/SystemUI/res-keyguard/values-hu/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-hu/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Az eszköz újraindítását követően meg kell adni a mintát"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Az eszköz újraindítását követően meg kell adni a PIN-kódot"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Az eszköz újraindítását követően meg kell adni a jelszót"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"A nagyobb biztonság érdekében használjon inkább mintát"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"A nagyobb biztonság érdekében használjon inkább PIN-kódot"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"A nagyobb biztonság érdekében használjon inkább jelszót"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"A rendszergazda zárolta az eszközt"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Az eszközt manuálisan lezárták"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Nem ismerhető fel"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Alapértelmezett"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Buborék"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analóg"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"A folytatáshoz oldja fel az eszközét"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-hy/strings.xml b/packages/SystemUI/res-keyguard/values-hy/strings.xml
index 086eeb9..d421c29 100644
--- a/packages/SystemUI/res-keyguard/values-hy/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-hy/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Սարքը վերագործարկելուց հետո անհրաժեշտ է մուտքագրել նախշը"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Սարքը վերագործարկելուց հետո անհրաժեշտ է մուտքագրել PIN կոդը"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Սարքը վերագործարկելուց հետո անհրաժեշտ է մուտքագրել գաղտնաբառը"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Լրացուցիչ անվտանգության համար օգտագործեք նախշ"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Լրացուցիչ անվտանգության համար օգտագործեք PIN կոդ"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Լրացուցիչ անվտանգության համար օգտագործեք գաղտնաբառ"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Սարքը կողպված է ադմինիստրատորի կողմից"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Սարքը կողպվել է ձեռքով"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Չհաջողվեց ճանաչել"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Կանխադրված"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Պղպջակ"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Անալոգային"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Շարունակելու համար ապակողպեք ձեր սարքը"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-in/strings.xml b/packages/SystemUI/res-keyguard/values-in/strings.xml
index b43a032..2061e85 100644
--- a/packages/SystemUI/res-keyguard/values-in/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-in/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Pola diperlukan setelah perangkat dimulai ulang"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN diperlukan setelah perangkat dimulai ulang"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Sandi diperlukan setelah perangkat dimulai ulang"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Untuk keamanan tambahan, gunakan pola"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Untuk keamanan tambahan, gunakan PIN"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Untuk keamanan tambahan, gunakan sandi"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Perangkat dikunci oleh admin"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Perangkat dikunci secara manual"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Tidak dikenali"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Default"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Balon"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Buka kunci perangkat untuk melanjutkan"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-is/strings.xml b/packages/SystemUI/res-keyguard/values-is/strings.xml
index 8bad961..ae3da57 100644
--- a/packages/SystemUI/res-keyguard/values-is/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-is/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Mynsturs er krafist þegar tækið er endurræst"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN-númers er krafist þegar tækið er endurræst"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Aðgangsorðs er krafist þegar tækið er endurræst"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Fyrir aukið öryggi skaltu nota mynstur í staðinn"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Fyrir aukið öryggi skaltu nota PIN-númer í staðinn"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Fyrir aukið öryggi skaltu nota aðgangsorð í staðinn"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Kerfisstjóri læsti tæki"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Tækinu var læst handvirkt"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Þekktist ekki"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Sjálfgefið"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Blaðra"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Með vísum"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Taktu tækið úr lás til að halda áfram"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-it/strings.xml b/packages/SystemUI/res-keyguard/values-it/strings.xml
index 186177ff..d1feea6 100644
--- a/packages/SystemUI/res-keyguard/values-it/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-it/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Sequenza obbligatoria dopo il riavvio del dispositivo"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN obbligatorio dopo il riavvio del dispositivo"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Password obbligatoria dopo il riavvio del dispositivo"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Per maggior sicurezza, usa invece la sequenza"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Per maggior sicurezza, usa invece il PIN"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Per maggior sicurezza, usa invece la password"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Dispositivo bloccato dall\'amministratore"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Il dispositivo è stato bloccato manualmente"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Non riconosciuto"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Predefinito"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Bolla"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analogico"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Sblocca il dispositivo per continuare"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-ka/strings.xml b/packages/SystemUI/res-keyguard/values-ka/strings.xml
index 123cc39..b56042a0 100644
--- a/packages/SystemUI/res-keyguard/values-ka/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ka/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"მოწყობილობის გადატვირთვის შემდეგ საჭიროა ნიმუშის დახატვა"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"მოწყობილობის გადატვირთვის შემდეგ საჭიროა PIN-კოდის შეყვანა"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"მოწყობილობის გადატვირთვის შემდეგ საჭიროა პაროლის შეყვანა"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"დამატებითი უსაფრთხოებისთვის გამოიყენეთ ნიმუში"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"დამატებითი უსაფრთხოებისთვის გამოიყენეთ PIN"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"დამატებითი უსაფრთხოებისთვის გამოიყენეთ პაროლი"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"მოწყობილობა ჩაკეტილია ადმინისტრატორის მიერ"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"მოწყობილობა ხელით ჩაიკეტა"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"არ არის ამოცნობილი"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"ნაგულისხმევი"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"ბუშტი"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"ანალოგური"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"გასაგრძელებლად განბლოკეთ თქვენი მოწყობილობა"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-kk/strings.xml b/packages/SystemUI/res-keyguard/values-kk/strings.xml
index 8daca5c..a4024de 100644
--- a/packages/SystemUI/res-keyguard/values-kk/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-kk/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Құрылғы қайта іске қосылғаннан кейін, өрнекті енгізу қажет"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Құрылғы қайта іске қосылғаннан кейін, PIN кодын енгізу қажет"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Құрылғы қайта іске қосылғаннан кейін, құпия сөзді енгізу қажет"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Қосымша қауіпсіздік үшін өрнекті пайдаланыңыз."</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Қосымша қауіпсіздік үшін PIN кодын пайдаланыңыз."</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Қосымша қауіпсіздік үшін құпия сөзді пайдаланыңыз."</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Құрылғыны әкімші құлыптаған"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Құрылғы қолмен құлыпталды"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Танылмады"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Әдепкі"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Көпіршік"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Аналогтық"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Жалғастыру үшін құрылғының құлпын ашыңыз"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-km/strings.xml b/packages/SystemUI/res-keyguard/values-km/strings.xml
index 73f507c..329912ab 100644
--- a/packages/SystemUI/res-keyguard/values-km/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-km/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"តម្រូវឲ្យប្រើលំនាំ បន្ទាប់ពីឧបករណ៍ចាប់ផ្តើមឡើងវិញ"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"តម្រូវឲ្យបញ្ចូលកូដ PIN បន្ទាប់ពីឧបករណ៍ចាប់ផ្តើមឡើងវិញ"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"តម្រូវឲ្យបញ្ចូលពាក្យសម្ងាត់ បន្ទាប់ពីឧបករណ៍ចាប់ផ្តើមឡើងវិញ"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"ដើម្បីសុវត្ថិភាពបន្ថែម សូមប្រើលំនាំជំនួសវិញ"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"ដើម្បីសុវត្ថិភាពបន្ថែម សូមប្រើកូដ PIN ជំនួសវិញ"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"ដើម្បីសុវត្ថិភាពបន្ថែម សូមប្រើពាក្យសម្ងាត់ជំនួសវិញ"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"ឧបករណ៍​ត្រូវបាន​ចាក់សោ​ដោយអ្នក​គ្រប់គ្រង"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"ឧបករណ៍ត្រូវបានចាក់សោដោយអ្នកប្រើផ្ទាល់"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"មិនអាចសម្គាល់បានទេ"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"លំនាំដើម"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"ពពុះ"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"អាណាឡូក"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"ដោះសោឧបករណ៍របស់អ្នកដើម្បីបន្ត"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-kn/strings.xml b/packages/SystemUI/res-keyguard/values-kn/strings.xml
index c279cea..d42d08d 100644
--- a/packages/SystemUI/res-keyguard/values-kn/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-kn/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ಸಾಧನ ಮರುಪ್ರಾರಂಭಗೊಂಡ ನಂತರ ಪ್ಯಾಟರ್ನ್ ಅಗತ್ಯವಿರುತ್ತದೆ"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ಸಾಧನ ಮರುಪ್ರಾರಂಭಗೊಂಡ ನಂತರ ಪಿನ್ ಅಗತ್ಯವಿರುತ್ತದೆ"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ಸಾಧನ ಮರುಪ್ರಾರಂಭಗೊಂಡ ನಂತರ ಪಾಸ್‌ವರ್ಡ್ ಅಗತ್ಯವಿರುತ್ತದೆ"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"ಹೆಚ್ಚುವರಿ ಭದ್ರತೆಗಾಗಿ, ಬದಲಿಗೆ ಪ್ಯಾಟರ್ನ್ ಅನ್ನು ಬಳಸಿ"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"ಹೆಚ್ಚುವರಿ ಭದ್ರತೆಗಾಗಿ, ಬದಲಿಗೆ ಪಿನ್ ಬಳಸಿ"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"ಹೆಚ್ಚುವರಿ ಭದ್ರತೆಗಾಗಿ, ಬದಲಿಗೆ ಪಾಸ್‌ವರ್ಡ್ ಅನ್ನು ಬಳಸಿ"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"ನಿರ್ವಾಹಕರು ಸಾಧನವನ್ನು ಲಾಕ್ ಮಾಡಿದ್ದಾರೆ"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"ಸಾಧನವನ್ನು ಹಸ್ತಚಾಲಿತವಾಗಿ ಲಾಕ್‌ ಮಾಡಲಾಗಿದೆ"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"ಗುರುತಿಸಲಾಗಿಲ್ಲ"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"ಡೀಫಾಲ್ಟ್"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"ಬಬಲ್"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"ಅನಲಾಗ್"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"ಮುಂದುವರಿಸಲು, ನಿಮ್ಮ ಸಾಧನವನ್ನು ಅನ್‌ಲಾಕ್ ಮಾಡಿ"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-ko/strings.xml b/packages/SystemUI/res-keyguard/values-ko/strings.xml
index 4c058ed..e916fee 100644
--- a/packages/SystemUI/res-keyguard/values-ko/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ko/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"기기가 다시 시작되면 패턴이 필요합니다."</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"기기가 다시 시작되면 PIN이 필요합니다."</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"기기가 다시 시작되면 비밀번호가 필요합니다."</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"보안 강화를 위해 대신 패턴 사용"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"보안 강화를 위해 대신 PIN 사용"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"보안 강화를 위해 대신 비밀번호 사용"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"관리자가 기기를 잠갔습니다."</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"기기가 수동으로 잠금 설정되었습니다."</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"인식할 수 없음"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"기본"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"버블"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"아날로그"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"기기를 잠금 해제하여 계속"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-ky/strings.xml b/packages/SystemUI/res-keyguard/values-ky/strings.xml
index 7c7099e..88abd1e 100644
--- a/packages/SystemUI/res-keyguard/values-ky/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ky/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Түзмөк кайра күйгүзүлгөндөн кийин графикалык ачкычты тартуу талап кылынат"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Түзмөк кайра күйгүзүлгөндөн кийин PIN-кодду киргизүү талап кылынат"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Түзмөк кайра күйгүзүлгөндөн кийин сырсөздү киргизүү талап кылынат"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Кошумча коопсуздук үчүн анын ордуна графикалык ачкычты колдонуңуз"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Кошумча коопсуздук үчүн анын ордуна PIN кодду колдонуңуз"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Кошумча коопсуздук үчүн анын ордуна сырсөздү колдонуңуз"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Түзмөктү администратор кулпулап койгон"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Түзмөк кол менен кулпуланды"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Таанылган жок"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Демейки"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Көбүк"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Аналог"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Улантуу үчүн түзмөгүңүздүн кулпусун ачыңыз"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-lo/strings.xml b/packages/SystemUI/res-keyguard/values-lo/strings.xml
index 5a6df42..5001c30 100644
--- a/packages/SystemUI/res-keyguard/values-lo/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-lo/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ຈຳເປັນຕ້ອງມີແບບຮູບປົດລັອກຫຼັງຈາກອຸປະກອນເລີ່ມລະບົບໃໝ່"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ຈຳເປັນຕ້ອງມີ PIN ຫຼັງຈາກອຸປະກອນເລີ່ມລະບົບໃໝ່"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ຈຳເປັນຕ້ອງມີລະຫັດຜ່ານຫຼັງຈາກອຸປະກອນເລີ່ມລະບົບໃໝ່"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"ເພື່ອຄວາມປອດໄພເພີ່ມເຕີມ, ໃຫ້ໃຊ້ຮູບແບບແທນ"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"ເພື່ອຄວາມປອດໄພເພີ່ມເຕີມ, ໃຫ້ໃຊ້ PIN ແທນ"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"ເພື່ອຄວາມປອດໄພເພີ່ມເຕີມ, ໃຫ້ໃຊ້ລະຫັດຜ່ານແທນ"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"ອຸປະກອນຖືກລັອກໂດຍຜູ້ເບິ່ງແຍງລະບົບ"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"ອຸປະກອນຖືກສັ່ງໃຫ້ລັອກ"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"ບໍ່ຮູ້ຈັກ"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"ຄ່າເລີ່ມຕົ້ນ"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"ຟອງ"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"ໂມງເຂັມ"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"ປົດລັອກອຸປະກອນຂອງທ່ານເພື່ອສືບຕໍ່"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-lt/strings.xml b/packages/SystemUI/res-keyguard/values-lt/strings.xml
index 4d98fd1..20f6ad2 100644
--- a/packages/SystemUI/res-keyguard/values-lt/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-lt/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Iš naujo paleidus įrenginį būtinas atrakinimo piešinys"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Iš naujo paleidus įrenginį būtinas PIN kodas"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Iš naujo paleidus įrenginį būtinas slaptažodis"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Papildomai saugai užtikrinti geriau naudokite atrakinimo piešinį"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Papildomai saugai užtikrinti geriau naudokite PIN kodą"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Papildomai saugai užtikrinti geriau naudokite slaptažodį"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Įrenginį užrakino administratorius"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Įrenginys užrakintas neautomatiškai"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Neatpažinta"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Numatytasis"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Debesėlis"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analoginis"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Įrenginio atrakinimas norint tęsti"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-lv/strings.xml b/packages/SystemUI/res-keyguard/values-lv/strings.xml
index 2660a06..7012c16 100644
--- a/packages/SystemUI/res-keyguard/values-lv/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-lv/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Pēc ierīces restartēšanas ir jāievada atbloķēšanas kombinācija."</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Pēc ierīces restartēšanas ir jāievada PIN kods."</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Pēc ierīces restartēšanas ir jāievada parole."</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Papildu drošībai izmantojiet kombināciju"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Papildu drošībai izmantojiet PIN"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Papildu drošībai izmantojiet paroli"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Administrators bloķēja ierīci."</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Ierīce tika bloķēta manuāli."</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Nav atpazīts"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Noklusējums"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Burbuļi"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analogais"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Lai turpinātu, atbloķējiet ierīci"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-ml/strings.xml b/packages/SystemUI/res-keyguard/values-ml/strings.xml
index e62b435..7919773 100644
--- a/packages/SystemUI/res-keyguard/values-ml/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ml/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ഉപകരണം റീസ്റ്റാർട്ടായശേഷം ‌പാറ്റേൺ വരയ്‌ക്കേണ്ടതുണ്ട്"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ഉപകരണം റീസ്റ്റാർട്ടായശേഷം ‌പിൻ നൽകേണ്ടതുണ്ട്"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ഉപകരണം റീസ്റ്റാർട്ടായശേഷം ‌പാസ്‌വേഡ് നൽകേണ്ടതുണ്ട്"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"അധിക സുരക്ഷയ്ക്കായി, പകരം പാറ്റേൺ ഉപയോഗിക്കുക"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"അധിക സുരക്ഷയ്ക്കായി, പകരം പിൻ ഉപയോഗിക്കുക"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"അധിക സുരക്ഷയ്ക്കായി, പകരം പാസ്‍വേഡ് ഉപയോഗിക്കുക"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"ഉപകരണം അഡ്‌മിൻ ലോക്കുചെയ്തു"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"ഉപകരണം നേരിട്ട് ലോക്കുചെയ്തു"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"തിരിച്ചറിയുന്നില്ല"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"ഡിഫോൾട്ട്"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"ബബിൾ"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"അനലോഗ്"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"തുടരാൻ നിങ്ങളുടെ ഉപകരണം അൺലോക്ക് ചെയ്യുക"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-mr/strings.xml b/packages/SystemUI/res-keyguard/values-mr/strings.xml
index 1454b20..580b547a 100644
--- a/packages/SystemUI/res-keyguard/values-mr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-mr/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"डिव्हाइस रीस्टार्ट झाल्यावर पॅटर्न आवश्यक आहे"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"डिव्हाइस रीस्टार्ट झाल्यावर पिन आवश्यक आहे"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"डिव्हाइस रीस्टार्ट झाल्यावर पासवर्ड आवश्यक आहे"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"अतिरिक्त सुरक्षेसाठी, त्याऐवजी पॅटर्न वापरा"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"अतिरिक्त सुरक्षेसाठी, त्याऐवजी पिन वापरा"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"अतिरिक्त सुरक्षेसाठी, त्याऐवजी पासवर्ड वापरा"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"प्रशासकाद्वारे लॉक केलेले डिव्हाइस"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"डिव्हाइस मॅन्युअली लॉक केले होते"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"ओळखले नाही"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"डीफॉल्ट"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"बबल"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"अ‍ॅनालॉग"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"पुढे सुरू ठेवण्यासाठी तुमचे डिव्हाइस अनलॉक करा"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-ms/strings.xml b/packages/SystemUI/res-keyguard/values-ms/strings.xml
index a6d1af9..c179dcb 100644
--- a/packages/SystemUI/res-keyguard/values-ms/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ms/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Corak diperlukan setelah peranti dimulakan semula"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"PIN diperlukan setelah peranti dimulakan semula"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Kata laluan diperlukan setelah peranti dimulakan semula"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Untuk keselamatan tambahan, gunakan corak"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Untuk keselamatan tambahan, gunakan PIN"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Untuk keselamatan tambahan, gunakan kata laluan"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Peranti dikunci oleh pentadbir"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Peranti telah dikunci secara manual"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Tidak dikenali"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Lalai"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Gelembung"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Buka kunci peranti anda untuk meneruskan"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-my/strings.xml b/packages/SystemUI/res-keyguard/values-my/strings.xml
index 5617a11..7c69bdd 100644
--- a/packages/SystemUI/res-keyguard/values-my/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-my/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"စက်ပစ္စည်းကို ပိတ်ပြီးပြန်ဖွင့်လိုက်သည့်အခါတွင် ပုံစံ လိုအပ်ပါသည်"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"စက်ပစ္စည်းကို ပိတ်ပြီးပြန်ဖွင့်လိုက်သည့်အခါတွင် ပင်နံပါတ် လိုအပ်ပါသည်"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"စက်ပစ္စည်းကို ပိတ်ပြီးပြန်ဖွင့်လိုက်သည့်အခါတွင် စကားဝှက် လိုအပ်ပါသည်"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"ထပ်ဆောင်းလုံခြုံရေးအတွက် ၎င်းအစား ပုံစံသုံးပါ"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"ထပ်ဆောင်းလုံခြုံရေးအတွက် ၎င်းအစား ပင်နံပါတ်သုံးပါ"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"ထပ်ဆောင်းလုံခြုံရေးအတွက် ၎င်းအစား စကားဝှက်သုံးပါ"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"စက်ပစ္စည်းကို စီမံခန့်ခွဲသူက လော့ခ်ချထားပါသည်"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"စက်ပစ္စည်းကို ကိုယ်တိုင်ကိုယ်ကျ လော့ခ်ချထားခဲ့သည်"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"မသိ"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"မူလ"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"ပူဖောင်းကွက်"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"ရိုးရိုး"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"ရှေ့ဆက်ရန် သင့်စက်ကိုဖွင့်ပါ"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-nb/strings.xml b/packages/SystemUI/res-keyguard/values-nb/strings.xml
index 0ad9e95..e394d1f 100644
--- a/packages/SystemUI/res-keyguard/values-nb/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-nb/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Du må tegne mønsteret etter at enheten har startet på nytt"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Du må skrive inn PIN-koden etter at enheten har startet på nytt"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Du må skrive inn passordet etter at enheten har startet på nytt"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Bruk mønster i stedet, for å øke sikkerheten"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Bruk PIN-kode i stedet, for å øke sikkerheten"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Bruk passord i stedet, for å øke sikkerheten"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Enheten er låst av administratoren"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Enheten ble låst manuelt"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Ikke gjenkjent"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Standard"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Boble"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Lås opp enheten for å fortsette"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-ne/strings.xml b/packages/SystemUI/res-keyguard/values-ne/strings.xml
index 196b74a..9f329e9 100644
--- a/packages/SystemUI/res-keyguard/values-ne/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ne/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"यन्त्र पुनः सुरु भएपछि ढाँचा आवश्यक पर्दछ"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"यन्त्र पुनः सुरु भएपछि PIN आवश्यक पर्दछ"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"यन्त्र पुनः सुरु भएपछि पासवर्ड आवश्यक पर्दछ"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"अतिरिक्त सुरक्षाका लागि यो प्रमाणीकरण विधिको साटो प्याटर्न प्रयोग गर्नुहोस्"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"अतिरिक्त सुरक्षाका लागि यो प्रमाणीकरण विधिको साटो पिन प्रयोग गर्नुहोस्"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"अतिरिक्त सुरक्षाका लागि यो प्रमाणीकरण विधिको साटो पासवर्ड प्रयोग गर्नुहोस्"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"प्रशासकले यन्त्रलाई लक गर्नुभएको छ"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"यन्त्रलाई म्यानुअल तरिकाले लक गरिएको थियो"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"पहिचान भएन"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"डिफल्ट"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"बबल"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"एनालग"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"आफ्नो डिभाइस अनलक गरी जारी राख्नुहोस्"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-nl/strings.xml b/packages/SystemUI/res-keyguard/values-nl/strings.xml
index 747b3bb..579824a 100644
--- a/packages/SystemUI/res-keyguard/values-nl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-nl/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Patroon vereist nadat het apparaat opnieuw is opgestart"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Pincode vereist nadat het apparaat opnieuw is opgestart"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Wachtwoord vereist nadat het apparaat opnieuw is opgestart"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Gebruik in plaats daarvan het patroon voor extra beveiliging"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Gebruik in plaats daarvan de pincode voor extra beveiliging"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Gebruik in plaats daarvan het wachtwoord voor extra beveiliging"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Apparaat vergrendeld door beheerder"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Apparaat is handmatig vergrendeld"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Niet herkend"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Standaard"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Bel"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analoog"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Ontgrendel je apparaat om door te gaan"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-pa/strings.xml b/packages/SystemUI/res-keyguard/values-pa/strings.xml
index bf1a359a..5c3fff7 100644
--- a/packages/SystemUI/res-keyguard/values-pa/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pa/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"ਡੀਵਾਈਸ ਦੇ ਮੁੜ-ਚਾਲੂ ਹੋਣ \'ਤੇ ਪੈਟਰਨ ਦੀ ਲੋੜ ਹੈ"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"ਡੀਵਾਈਸ ਦੇ ਮੁੜ-ਚਾਲੂ ਹੋਣ \'ਤੇ ਪਿੰਨ ਦੀ ਲੋੜ ਹੈ"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"ਡੀਵਾਈਸ ਦੇ ਮੁੜ-ਚਾਲੂ ਹੋਣ \'ਤੇ ਪਾਸਵਰਡ ਦੀ ਲੋੜ ਹੈ"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"ਵਧੀਕ ਸੁਰੱਖਿਆ ਲਈ, ਇਸਦੀ ਬਜਾਏ ਪੈਟਰਨ ਵਰਤੋ"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"ਵਧੀਕ ਸੁਰੱਖਿਆ ਲਈ, ਇਸਦੀ ਬਜਾਏ ਪਿੰਨ ਵਰਤੋ"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"ਵਧੀਕ ਸੁਰੱਖਿਆ ਲਈ, ਇਸਦੀ ਬਜਾਏ ਪਾਸਵਰਡ ਵਰਤੋ"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"ਪ੍ਰਸ਼ਾਸਕ ਵੱਲੋਂ ਡੀਵਾਈਸ ਨੂੰ ਲਾਕ ਕੀਤਾ ਗਿਆ"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"ਡੀਵਾਈਸ ਨੂੰ ਹੱਥੀਂ ਲਾਕ ਕੀਤਾ ਗਿਆ"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"ਪਛਾਣ ਨਹੀਂ ਹੋਈ"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"ਪੂਰਵ-ਨਿਰਧਾਰਿਤ"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"ਬੁਲਬੁਲਾ"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"ਐਨਾਲੌਗ"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"ਜਾਰੀ ਰੱਖਣ ਲਈ ਆਪਣੇ ਡੀਵਾਈਸ ਨੂੰ ਅਣਲਾਕ ਕਰੋ"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-pl/strings.xml b/packages/SystemUI/res-keyguard/values-pl/strings.xml
index c49149b..3736386 100644
--- a/packages/SystemUI/res-keyguard/values-pl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-pl/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Po ponownym uruchomieniu urządzenia wymagany jest wzór"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Po ponownym uruchomieniu urządzenia wymagany jest kod PIN"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Po ponownym uruchomieniu urządzenia wymagane jest hasło"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Ze względów bezpieczeństwa użyj wzoru"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Ze względów bezpieczeństwa użyj kodu PIN"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Ze względów bezpieczeństwa użyj hasła"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Urządzenie zablokowane przez administratora"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Urządzenie zostało zablokowane ręcznie"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Nie rozpoznano"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Domyślna"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Bąbelkowy"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analogowy"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Odblokuj urządzenie, aby kontynuować"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-ro/strings.xml b/packages/SystemUI/res-keyguard/values-ro/strings.xml
index 547224e..67ae0fc 100644
--- a/packages/SystemUI/res-keyguard/values-ro/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ro/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Modelul este necesar după repornirea dispozitivului"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Codul PIN este necesar după repornirea dispozitivului"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Parola este necesară după repornirea dispozitivului"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Pentru mai multă securitate, folosește modelul"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Pentru mai multă securitate, folosește codul PIN"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Pentru mai multă securitate, folosește parola"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Dispozitiv blocat de administrator"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Dispozitivul a fost blocat manual"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Nu este recunoscut"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Prestabilit"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Balon"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analogic"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Deblochează dispozitivul pentru a continua"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-si/strings.xml b/packages/SystemUI/res-keyguard/values-si/strings.xml
index e5862c3..82df4cb 100644
--- a/packages/SystemUI/res-keyguard/values-si/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-si/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"උපාංගය නැවත ආරම්භ වූ පසු රටාව අවශ්‍යයි"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"උපාංගය නැවත ආරම්භ වූ පසු PIN අංකය අවශ්‍යයි"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"උපාංගය නැවත ආරම්භ වූ පසු මුරපදය අවශ්‍යයි"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"අතිරේක ආරක්ෂාව සඳහා, ඒ වෙනුවට රටාව භාවිතා කරන්න"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"අතිරේක ආරක්ෂාව සඳහා, ඒ වෙනුවට PIN භාවිතා කරන්න"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"අතිරේක ආරක්ෂාව සඳහා, ඒ වෙනුවට මුරපදය භාවිතා කරන්න"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"ඔබගේ පරිපාලක විසින් උපාංගය අගුළු දමා ඇත"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"උපාංගය හස්තීයව අගුලු දමන ලදී"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"හඳුනා නොගන්නා ලදී"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"පෙරනිමි"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"බුබුළ"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"ප්‍රතිසමය"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"ඉදිරියට යාමට ඔබේ උපාංගය අගුළු හරින්න"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-sk/strings.xml b/packages/SystemUI/res-keyguard/values-sk/strings.xml
index efe4ec8..2d8b3b1 100644
--- a/packages/SystemUI/res-keyguard/values-sk/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sk/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Po reštartovaní zariadenia musíte zadať bezpečnostný vzor"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Po reštartovaní zariadenia musíte zadať kód PIN"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Po reštartovaní zariadenia musíte zadať heslo"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"V rámci zvýšenia zabezpečenia použite radšej vzor"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"V rámci zvýšenia zabezpečenia použite radšej PIN"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"V rámci zvýšenia zabezpečenia použite radšej heslo"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Zariadenie zamkol správca"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Zariadenie bolo uzamknuté ručne"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Nerozpoznané"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Predvolený"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Bublina"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analógový"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Ak chcete pokračovať, odomknite zariadenie"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-sl/strings.xml b/packages/SystemUI/res-keyguard/values-sl/strings.xml
index 52726c2..4c4ea06 100644
--- a/packages/SystemUI/res-keyguard/values-sl/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sl/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Po vnovičnem zagonu naprave je treba vnesti vzorec"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Po vnovičnem zagonu naprave je treba vnesti kodo PIN"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Po vnovičnem zagonu naprave je treba vnesti geslo"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Za dodatno varnost raje uporabite vzorec."</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Za dodatno varnost raje uporabite kodo PIN."</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Za dodatno varnost raje uporabite geslo."</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Napravo je zaklenil skrbnik"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Naprava je bila ročno zaklenjena"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Ni prepoznano"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Privzeto"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Mehurček"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analogno"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Za nadaljevanje odklenite napravo"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-sq/strings.xml b/packages/SystemUI/res-keyguard/values-sq/strings.xml
index a0a5594..78e217d 100644
--- a/packages/SystemUI/res-keyguard/values-sq/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sq/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Kërkohet motivi pas rinisjes së pajisjes"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Kërkohet kodi PIN pas rinisjes së pajisjes"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Kërkohet fjalëkalimi pas rinisjes së pajisjes"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Për më shumë siguri, përdor motivin më mirë"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Për më shumë siguri, përdor kodin PIN më mirë"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Për më shumë siguri, përdor fjalëkalimin më mirë"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Pajisja është e kyçur nga administratori"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Pajisja është kyçur manualisht"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Nuk njihet"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"E parazgjedhur"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Flluskë"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analoge"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Shkyç pajisjen tënde për të vazhduar"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-sr/strings.xml b/packages/SystemUI/res-keyguard/values-sr/strings.xml
index e634fdcb5..80d8755 100644
--- a/packages/SystemUI/res-keyguard/values-sr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sr/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Треба да унесете шаблон када се уређај поново покрене"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Треба да унесете PIN када се уређај поново покрене"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Треба да унесете лозинку када се уређај поново покрене"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"За додатну безбедност користите шаблон"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"За додатну безбедност користите PIN"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"За додатну безбедност користите лозинку"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Администратор је закључао уређај"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Уређај је ручно закључан"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Није препознат"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Подразумевани"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Мехурићи"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Аналогни"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Откључајте уређај да бисте наставили"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-sv/strings.xml b/packages/SystemUI/res-keyguard/values-sv/strings.xml
index fc9beb1..b5548b9 100644
--- a/packages/SystemUI/res-keyguard/values-sv/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sv/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Du måste rita mönster när du har startat om enheten"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Du måste ange pinkod när du har startat om enheten"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Du måste ange lösenord när du har startat om enheten"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"För ytterligare säkerhet använder du mönstret i stället"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"För ytterligare säkerhet använder du pinkoden i stället"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"För ytterligare säkerhet använder du lösenordet i stället"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Administratören har låst enheten"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Enheten har låsts manuellt"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Identifierades inte"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Standard"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Bubbla"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Lås upp enheten för att fortsätta"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-sw/strings.xml b/packages/SystemUI/res-keyguard/values-sw/strings.xml
index bcab24b..02af18e 100644
--- a/packages/SystemUI/res-keyguard/values-sw/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-sw/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Unafaa kuchora mchoro baada ya kuwasha kifaa upya"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Unafaa kuweka PIN baada ya kuwasha kifaa upya"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Unafaa kuweka nenosiri baada ya kuwasha kifaa upya"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Kwa usalama wa ziada, tumia mchoro badala yake"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Kwa usalama wa ziada, tumia PIN badala yake"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Kwa usalama wa ziada, tumia nenosiri badala yake"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Msimamizi amefunga kifaa"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Umefunga kifaa mwenyewe"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Haitambuliwi"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Chaguomsingi"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Kiputo"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analogi"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Fungua kifaa chako ili uendelee"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-ta/strings.xml b/packages/SystemUI/res-keyguard/values-ta/strings.xml
index 88d5760..0d32d46 100644
--- a/packages/SystemUI/res-keyguard/values-ta/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ta/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"சாதனத்தை மீண்டும் தொடங்கியதும், பேட்டர்னை வரைய வேண்டும்"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"சாதனத்தை மீண்டும் தொடங்கியதும், பின்னை உள்ளிட வேண்டும்"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"சாதனத்தை மீண்டும் தொடங்கியதும், கடவுச்சொல்லை உள்ளிட வேண்டும்"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"கூடுதல் பாதுகாப்பிற்குப் பேட்டர்னைப் பயன்படுத்தவும்"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"கூடுதல் பாதுகாப்பிற்குப் பின்னை (PIN) பயன்படுத்தவும்"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"கூடுதல் பாதுகாப்பிற்குக் கடவுச்சொல்லைப் பயன்படுத்தவும்"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"நிர்வாகி சாதனத்தைப் பூட்டியுள்ளார்"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"பயனர் சாதனத்தைப் பூட்டியுள்ளார்"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"அடையாளங்காணபடவில்லை"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"இயல்பு"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"பபிள்"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"அனலாக்"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"தொடர, சாதனத்தை அன்லாக் செய்யுங்கள்"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-te/strings.xml b/packages/SystemUI/res-keyguard/values-te/strings.xml
index 3a0111a..f519daf 100644
--- a/packages/SystemUI/res-keyguard/values-te/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-te/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"పరికరాన్ని పునఃప్రారంభించిన తర్వాత నమూనాను గీయాలి"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"డివైజ్‌ను పునఃప్రారంభించిన తర్వాత పిన్ నమోదు చేయాలి"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"పరికరాన్ని పునఃప్రారంభించిన తర్వాత పాస్‌వర్డ్‌ను నమోదు చేయాలి"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"అదనపు సెక్యూరిటీ కోసం, బదులుగా ఆకృతిని ఉపయోగించండి"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"అదనపు సెక్యూరిటీ కోసం, బదులుగా PINను ఉపయోగించండి"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"అదనపు సెక్యూరిటీ కోసం, బదులుగా పాస్‌వర్డ్‌ను ఉపయోగించండి"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"పరికరం నిర్వాహకుల ద్వారా లాక్ చేయబడింది"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"పరికరం మాన్యువల్‌గా లాక్ చేయబడింది"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"గుర్తించలేదు"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"ఆటోమేటిక్"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"బబుల్"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"ఎనలాగ్"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"కొనసాగించడానికి మీ పరికరాన్ని అన్‌లాక్ చేయండి"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-tr/strings.xml b/packages/SystemUI/res-keyguard/values-tr/strings.xml
index e520762..80dae8c 100644
--- a/packages/SystemUI/res-keyguard/values-tr/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-tr/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Cihaz yeniden başladıktan sonra desen gerekir"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Cihaz yeniden başladıktan sonra PIN gerekir"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Cihaz yeniden başladıktan sonra şifre gerekir"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Ek güvenlik için bunun yerine desen kullanın"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Ek güvenlik için bunun yerine PIN kullanın"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Ek güvenlik için bunun yerine şifre kullanın"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Cihaz, yönetici tarafından kilitlendi"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Cihazın manuel olarak kilitlendi"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Tanınmadı"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Varsayılan"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Baloncuk"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Analog"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Devam etmek için cihazınızın kilidini açın"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-uk/strings.xml b/packages/SystemUI/res-keyguard/values-uk/strings.xml
index 613181d..ff594ae 100644
--- a/packages/SystemUI/res-keyguard/values-uk/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-uk/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Після перезавантаження пристрою потрібно ввести ключ"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Після перезавантаження пристрою потрібно ввести PIN-код"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Після перезавантаження пристрою потрібно ввести пароль"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"З міркувань додаткової безпеки скористайтеся ключем"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"З міркувань додаткової безпеки скористайтеся PIN-кодом"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"З міркувань додаткової безпеки скористайтеся паролем"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Адміністратор заблокував пристрій"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Пристрій заблоковано вручну"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Не розпізнано"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"За умовчанням"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Бульбашковий"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Аналоговий"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Розблокуйте пристрій, щоб продовжити"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-ur/strings.xml b/packages/SystemUI/res-keyguard/values-ur/strings.xml
index a122f85..9308260 100644
--- a/packages/SystemUI/res-keyguard/values-ur/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ur/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"آلہ دوبارہ چالو ہونے کے بعد پیٹرن درکار ہوتا ہے"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"‏آلہ دوبارہ چالو ہونے کے بعد PIN درکار ہوتا ہے"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"آلہ دوبارہ چالو ہونے کے بعد پاس ورڈ درکار ہوتا ہے"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"اضافی سیکیورٹی کے لئے، اس کے بجائے پیٹرن استعمال کریں"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"‏اضافی سیکیورٹی کے لئے، اس کے بجائے PIN استعمال کریں"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"اضافی سیکیورٹی کے لئے، اس کے بجائے پاس ورڈ استعمال کریں"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"آلہ منتظم کی جانب سے مقفل ہے"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"آلہ کو دستی طور پر مقفل کیا گیا تھا"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"تسلیم شدہ نہیں ہے"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"ڈیفالٹ"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"بلبلہ"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"اینالاگ"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"جاری رکھنے کے لئے اپنا آلہ غیر مقفل کریں"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-vi/strings.xml b/packages/SystemUI/res-keyguard/values-vi/strings.xml
index e7c9295..2771ada 100644
--- a/packages/SystemUI/res-keyguard/values-vi/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-vi/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Yêu cầu hình mở khóa sau khi thiết bị khởi động lại"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Yêu cầu mã PIN sau khi thiết bị khởi động lại"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Yêu cầu mật khẩu sau khi thiết bị khởi động lại"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Để tăng cường bảo mật, hãy sử dụng hình mở khoá"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Để tăng cường bảo mật, hãy sử dụng mã PIN"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Để tăng cường bảo mật, hãy sử dụng mật khẩu"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Thiết bị đã bị quản trị viên khóa"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Thiết bị đã bị khóa theo cách thủ công"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Không nhận dạng được"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Mặc định"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Bong bóng"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"Đồng hồ kim"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Mở khoá thiết bị của bạn để tiếp tục"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml b/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml
index d37d645..fb92838 100644
--- a/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-zh-rCN/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"重启设备后需要绘制解锁图案"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"重启设备后需要输入 PIN 码"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"重启设备后需要输入密码"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"为增强安全性,请改用图案"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"为增强安全性,请改用 PIN 码"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"为增强安全性,请改用密码"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"管理员已锁定设备"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"此设备已手动锁定"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"无法识别"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"默认"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"泡泡"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"指针"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"解锁设备才能继续操作"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-zh-rHK/strings.xml b/packages/SystemUI/res-keyguard/values-zh-rHK/strings.xml
index 9dbb8f2..49050e5 100644
--- a/packages/SystemUI/res-keyguard/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-zh-rHK/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"裝置重新啟動後,必須畫出上鎖圖案才能使用"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"裝置重新啟動後,必須輸入 PIN 碼才能使用"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"裝置重新啟動後,必須輸入密碼才能使用"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"為提升安全性,請改用圖案"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"為提升安全性,請改用 PIN"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"為提升安全性,請改用密碼"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"裝置已由管理員鎖定"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"使用者已手動將裝置上鎖"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"未能識別"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"預設"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"泡泡"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"指針"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"解鎖裝置以繼續"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-zh-rTW/strings.xml b/packages/SystemUI/res-keyguard/values-zh-rTW/strings.xml
index ebb88e1..e5a363c 100644
--- a/packages/SystemUI/res-keyguard/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-zh-rTW/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"裝置重新啟動後需要畫出解鎖圖案"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"裝置重新啟動後需要輸入 PIN 碼"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"裝置重新啟動後需要輸入密碼"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"為強化安全性,請改用解鎖圖案"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"為強化安全性,請改用 PIN 碼"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"為強化安全性,請改用密碼"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"管理員已鎖定裝置"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"裝置已手動鎖定"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"無法識別"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"預設"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"泡泡"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"類比"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"解鎖裝置才能繼續操作"</string>
 </resources>
diff --git a/packages/SystemUI/res-keyguard/values-zu/strings.xml b/packages/SystemUI/res-keyguard/values-zu/strings.xml
index 57e56f7..72ca6c0 100644
--- a/packages/SystemUI/res-keyguard/values-zu/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-zu/strings.xml
@@ -78,12 +78,9 @@
     <string name="kg_prompt_reason_restart_pattern" msgid="4720554342633852066">"Iphethini iyadingeka ngemuva kokuqala kabusha kwedivayisi"</string>
     <string name="kg_prompt_reason_restart_pin" msgid="1587671566498057656">"Iphinikhodi iyadingeka ngemuva kokuqala kabusha kwedivayisi"</string>
     <string name="kg_prompt_reason_restart_password" msgid="8061279087240952002">"Iphasiwedi iyadingeka ngemuva kokuqala kabusha kwedivayisi"</string>
-    <!-- no translation found for kg_prompt_reason_timeout_pattern (5514969660010197363) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_pin (4227962059353859376) -->
-    <skip />
-    <!-- no translation found for kg_prompt_reason_timeout_password (8810879144143933690) -->
-    <skip />
+    <string name="kg_prompt_reason_timeout_pattern" msgid="5514969660010197363">"Ukuze uthole ukuvikeleka okwengeziwe, sebenzisa iphetheni esikhundleni salokho"</string>
+    <string name="kg_prompt_reason_timeout_pin" msgid="4227962059353859376">"Ukuze uthole ukuvikeleka okwengeziwe, sebenzisa i-PIN esikhundleni salokho"</string>
+    <string name="kg_prompt_reason_timeout_password" msgid="8810879144143933690">"Ukuze uthole ukuvikeleka okwengeziwe, sebenzisa iphasiwedi esikhundleni salokho"</string>
     <string name="kg_prompt_reason_device_admin" msgid="6961159596224055685">"Idivayisi ikhiywe ngumlawuli"</string>
     <string name="kg_prompt_reason_user_request" msgid="6015774877733717904">"Idivayisi ikhiywe ngokwenza"</string>
     <string name="kg_face_not_recognized" msgid="7903950626744419160">"Akwaziwa"</string>
@@ -93,6 +90,5 @@
     <string name="clock_title_default" msgid="6342735240617459864">"Okuzenzekelayo"</string>
     <string name="clock_title_bubble" msgid="2204559396790593213">"Ibhamuza"</string>
     <string name="clock_title_analog" msgid="8409262532900918273">"I-Analog"</string>
-    <!-- no translation found for keyguard_unlock_to_continue (7509503484250597743) -->
-    <skip />
+    <string name="keyguard_unlock_to_continue" msgid="7509503484250597743">"Vula idivayisi yakho ukuze uqhubeke"</string>
 </resources>
diff --git a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
index 006b260..9add32c 100644
--- a/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
+++ b/packages/SystemUI/res/layout/dream_overlay_status_bar_view.xml
@@ -18,6 +18,7 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:id="@+id/dream_overlay_status_bar"
+    android:visibility="invisible"
     android:layout_width="match_parent"
     android:layout_height="@dimen/dream_overlay_status_bar_height"
     android:paddingEnd="@dimen/dream_overlay_status_bar_margin"
diff --git a/packages/SystemUI/res/layout/media_session_view.xml b/packages/SystemUI/res/layout/media_session_view.xml
index c526d9c..9b8b611 100644
--- a/packages/SystemUI/res/layout/media_session_view.xml
+++ b/packages/SystemUI/res/layout/media_session_view.xml
@@ -44,6 +44,15 @@
         android:background="@drawable/qs_media_outline_album_bg"
         />
 
+    <com.android.systemui.ripple.MultiRippleView
+        android:id="@+id/touch_ripple_view"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/qs_media_session_height_expanded"
+        app:layout_constraintStart_toStartOf="@id/album_art"
+        app:layout_constraintEnd_toEndOf="@id/album_art"
+        app:layout_constraintTop_toTopOf="@id/album_art"
+        app:layout_constraintBottom_toBottomOf="@id/album_art" />
+
     <!-- Guideline for output switcher -->
     <androidx.constraintlayout.widget.Guideline
         android:id="@+id/center_vertical_guideline"
diff --git a/packages/SystemUI/res/layout/status_bar_expanded.xml b/packages/SystemUI/res/layout/status_bar_expanded.xml
index 92ef3f8..159323a 100644
--- a/packages/SystemUI/res/layout/status_bar_expanded.xml
+++ b/packages/SystemUI/res/layout/status_bar_expanded.xml
@@ -42,12 +42,6 @@
         android:clipToPadding="false"
         android:clipChildren="false">
 
-        <ViewStub
-            android:id="@+id/qs_header_stub"
-            android:layout_height="wrap_content"
-            android:layout_width="match_parent"
-        />
-
         <include
             layout="@layout/keyguard_status_view"
             android:visibility="gone"/>
@@ -69,6 +63,15 @@
             systemui:layout_constraintBottom_toBottomOf="parent"
         />
 
+        <!-- This view should be after qs_frame so touches are dispatched first to it. That gives
+             it a chance to capture clicks before the NonInterceptingScrollView disallows all
+             intercepts -->
+        <ViewStub
+            android:id="@+id/qs_header_stub"
+            android:layout_height="wrap_content"
+            android:layout_width="match_parent"
+        />
+
         <androidx.constraintlayout.widget.Guideline
             android:id="@+id/qs_edge_guideline"
             android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/layout/user_switcher_fullscreen.xml b/packages/SystemUI/res/layout/user_switcher_fullscreen.xml
index c2c79cb..78884ff 100644
--- a/packages/SystemUI/res/layout/user_switcher_fullscreen.xml
+++ b/packages/SystemUI/res/layout/user_switcher_fullscreen.xml
@@ -14,58 +14,84 @@
      See the License for the specific language governing permissions and
      limitations under the License.
 -->
-<com.android.systemui.user.UserSwitcherRootView
+<LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
     android:id="@+id/user_switcher_root"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:layout_marginVertical="40dp"
-    android:layout_marginHorizontal="60dp">
+    android:orientation="vertical">
 
-  <androidx.constraintlayout.helper.widget.Flow
-      android:id="@+id/flow"
-      android:layout_width="0dp"
-      android:layout_height="wrap_content"
-      app:layout_constraintBottom_toBottomOf="parent"
-      app:layout_constraintTop_toTopOf="parent"
-      app:layout_constraintStart_toStartOf="parent"
-      app:layout_constraintEnd_toEndOf="parent"
-      app:flow_horizontalBias="0.5"
-      app:flow_verticalAlign="center"
-      app:flow_wrapMode="chain"
-      app:flow_horizontalGap="@dimen/user_switcher_fullscreen_horizontal_gap"
-      app:flow_verticalGap="44dp"
-      app:flow_horizontalStyle="packed"/>
+  <ScrollView
+      android:layout_width="match_parent"
+      android:layout_height="0dp"
+      android:layout_weight="1"
+      android:fillViewport="true">
 
-  <TextView
-      android:id="@+id/cancel"
-      android:layout_width="wrap_content"
-      android:layout_height="wrap_content"
-      android:layout_gravity="center"
-      android:gravity="center"
-      app:layout_constraintHeight_min="48dp"
-      app:layout_constraintEnd_toStartOf="@+id/add"
-      app:layout_constraintBottom_toBottomOf="parent"
-      android:paddingHorizontal="@dimen/user_switcher_fullscreen_button_padding"
-      android:textSize="@dimen/user_switcher_fullscreen_button_text_size"
-      android:textColor="?androidprv:attr/colorAccentPrimary"
-      android:text="@string/cancel" />
+    <com.android.systemui.user.UserSwitcherRootView
+        android:id="@+id/user_switcher_grid_container"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:paddingTop="40dp"
+        android:paddingHorizontal="60dp">
 
-  <TextView
-      android:id="@+id/add"
-      style="@style/Widget.Dialog.Button.BorderButton"
-      android:layout_width="wrap_content"
-      android:layout_height="wrap_content"
-      android:layout_gravity="center"
-      android:gravity="center"
-      android:paddingHorizontal="@dimen/user_switcher_fullscreen_button_padding"
-      android:text="@string/add"
-      android:textColor="?androidprv:attr/colorAccentPrimary"
-      android:textSize="@dimen/user_switcher_fullscreen_button_text_size"
-      android:visibility="gone"
-      app:layout_constraintBottom_toBottomOf="parent"
-      app:layout_constraintEnd_toEndOf="parent"
-      app:layout_constraintHeight_min="48dp" />
-</com.android.systemui.user.UserSwitcherRootView>
+      <androidx.constraintlayout.helper.widget.Flow
+          android:id="@+id/flow"
+          android:layout_width="0dp"
+          android:layout_height="wrap_content"
+          app:layout_constraintBottom_toBottomOf="parent"
+          app:layout_constraintTop_toTopOf="parent"
+          app:layout_constraintStart_toStartOf="parent"
+          app:layout_constraintEnd_toEndOf="parent"
+          app:flow_horizontalBias="0.5"
+          app:flow_verticalAlign="center"
+          app:flow_wrapMode="chain"
+          app:flow_horizontalGap="@dimen/user_switcher_fullscreen_horizontal_gap"
+          app:flow_verticalGap="44dp"
+          app:flow_horizontalStyle="packed"/>
+    </com.android.systemui.user.UserSwitcherRootView>
+
+  </ScrollView>
+
+  <LinearLayout
+    android:layout_width="match_parent"
+    android:layout_height="96dp"
+    android:orientation="horizontal"
+    android:gravity="center_vertical|end"
+    android:paddingEnd="48dp">
+
+    <TextView
+        android:id="@+id/cancel"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:gravity="center"
+        android:minHeight="48dp"
+        android:paddingHorizontal="@dimen/user_switcher_fullscreen_button_padding"
+        android:textSize="@dimen/user_switcher_fullscreen_button_text_size"
+        android:textColor="?androidprv:attr/colorAccentPrimary"
+        android:text="@string/cancel" />
+
+    <Space
+        android:layout_width="24dp"
+        android:layout_height="0dp"
+        />
+
+    <TextView
+        android:id="@+id/add"
+        style="@style/Widget.Dialog.Button.BorderButton"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:gravity="center"
+        android:paddingHorizontal="@dimen/user_switcher_fullscreen_button_padding"
+        android:text="@string/add"
+        android:textColor="?androidprv:attr/colorAccentPrimary"
+        android:textSize="@dimen/user_switcher_fullscreen_button_text_size"
+        android:visibility="gone"
+        android:minHeight="48dp" />
+
+  </LinearLayout>
+
+</LinearLayout>
diff --git a/packages/SystemUI/res/values-af/strings.xml b/packages/SystemUI/res/values-af/strings.xml
index 3dc85d70..2f2780b 100644
--- a/packages/SystemUI/res/values-af/strings.xml
+++ b/packages/SystemUI/res/values-af/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Helderheid"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Kleuromkering"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Kleurregstelling"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Gebruikerinstellings"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"Klaar"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Maak toe"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"Gekoppel"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"Skakel mobiele data af?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"Jy sal nie deur <xliff:g id="CARRIER">%s</xliff:g> toegang tot data of die internet hê nie. Internet sal net deur Wi-Fi beskikbaar wees."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"jou diensverskaffer"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"Instellings kan nie jou antwoord verifieer nie omdat \'n program \'n toestemmingversoek verberg."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"Laat <xliff:g id="APP_0">%1$s</xliff:g> toe om <xliff:g id="APP_2">%2$s</xliff:g>-skyfies te wys?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"– Dit kan inligting van <xliff:g id="APP">%1$s</xliff:g> af lees"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Vergrootglasvensterinstellings"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Tik om toeganklikheidkenmerke oop te maak Pasmaak of vervang knoppie in Instellings.\n\n"<annotation id="link">"Bekyk instellings"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Skuif knoppie na kant om dit tydelik te versteek"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Beweeg na links bo"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Beweeg na regs bo"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Beweeg na links onder"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Beweeg na regs onder"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Beweeg na rand en versteek"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Beweeg weg van rand en wys"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"wissel"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Toestelkontroles"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"Kies program om kontroles by te voeg"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobiele data"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"Gekoppel"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobiele data sal nie outomaties koppel nie"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"Geen verbinding nie"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Geen ander netwerke beskikbaar nie"</string>
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index fb60f9b..3ccb686 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ብሩህነት"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"ተቃራኒ ቀለም"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"የቀለም ማስተካከያ"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"የተጠቃሚ ቅንብሮች"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"ተከናውኗል"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"ዝጋ"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"ተገናኝቷል"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"የተንቀሳቃሽ ስልክ ውሂብ ይጥፋ?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"በ<xliff:g id="CARRIER">%s</xliff:g> በኩል የውሂብ ወይም የበይነመረቡ መዳረሻ አይኖረዎትም። በይነመረብ በWi-Fi በኩል ብቻ ነው የሚገኝ የሚሆነው።"</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"የእርስዎ አገልግሎት አቅራቢ"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"አንድ መተግበሪያ የፍቃድ ጥያቄ እያገደ ስለሆነ ቅንብሮች ጥያቄዎን ማረጋገጥ አይችሉም።"</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> የ<xliff:g id="APP_2">%2$s</xliff:g> ቁራጮችን እንዲያሳይ ይፈቀድለት?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- ከ<xliff:g id="APP">%1$s</xliff:g> የመጣ መረጃን ማንበብ ይችላል"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"የማጉያ መስኮት ቅንብሮች"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"የተደራሽነት ባህሪያትን ለመክፈት መታ ያድርጉ። ይህንን አዝራር በቅንብሮች ውስጥ ያብጁ ወይም ይተኩ።\n\n"<annotation id="link">"ቅንብሮችን አሳይ"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"ለጊዜው ለመደበቅ አዝራሩን ወደ ጠርዝ ያንቀሳቅሱ"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ወደ ላይኛው ግራ አንቀሳቅስ"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"ወደ ላይኛው ቀኝ አንቀሳቅስ"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"የግርጌውን ግራ አንቀሳቅስ"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"ታችኛውን ቀኝ አንቀሳቅስ"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"ወደ ጠርዝ አንቀሳቅስ እና ደደብቅ"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"ጠርዙን ወደ ውጭ አንቀሳቅስ እና አሳይ"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"ቀያይር"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"የመሣሪያ መቆጣጠሪያዎች"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"መቆጣጠሪያዎችን ለማከል መተግበሪያ ይምረጡ"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"የተንቀሳቃሽ ስልክ ውሂብ"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"ተገናኝቷል"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"የተንቀሳቃሽ ስልክ ውሂብ በራስ-ሰር አይገናኝም"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"ግንኙነት የለም"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"ሌላ አውታረ መረብ የሉም"</string>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index f8567c7..c90fb25 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"السطوع"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"قلب الألوان"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"تصحيح الألوان"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"إعدادات المستخدم"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"تم"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"إغلاق"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"متصل"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"هل تريد إيقاف بيانات الجوّال؟"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"‏لن تتمكّن من استخدام البيانات أو الإنترنت من خلال <xliff:g id="CARRIER">%s</xliff:g>. ولن يتوفر اتصال الإنترنت إلا عبر Wi-Fi."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"مشغّل شبكة الجوّال"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"لا يمكن للإعدادات التحقق من ردك لأن هناك تطبيقًا يحجب طلب الإذن."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"هل تريد السماح لتطبيق <xliff:g id="APP_0">%1$s</xliff:g> بعرض شرائح <xliff:g id="APP_2">%2$s</xliff:g>؟"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- يستطيع قراءة المعلومات من <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"إعدادات نافذة مكبّر الشاشة"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"انقر لفتح ميزات تسهيل الاستخدام. يمكنك تخصيص هذا الزر أو استبداله من الإعدادات.\n\n"<annotation id="link">"عرض الإعدادات"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"يمكنك نقل الزر إلى الحافة لإخفائه مؤقتًا."</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"نقل إلى أعلى يمين الشاشة"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"نقل إلى أعلى يسار الشاشة"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"نقل إلى أسفل يمين الشاشة"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"نقل إلى أسفل يسار الشاشة"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"نقله إلى الحافة وإخفاؤه"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"نقله إلى خارج الحافة وإظهاره"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"إيقاف/تفعيل"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"التحكم بالجهاز"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"اختيار تطبيق لإضافة عناصر التحكّم"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"بيانات الجوّال"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"متصلة بالإنترنت"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"لن يتم تلقائيًا الاتصال ببيانات الجوّال."</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"لا يتوفّر اتصال بالإنترنت"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"لا تتوفّر شبكات أخرى."</string>
diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml
index 4fca5f2e..c363eee 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"উজ্জ্বলতা"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"ৰং বিপৰীতকৰণ"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"ৰং শুধৰণী"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"ব্যৱহাৰকাৰীৰ ছেটিং"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"সম্পন্ন কৰা হ’ল"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"বন্ধ কৰক"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"সংযোগ কৰা হ’ল"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"ম’বাইল ডেটা অফ কৰিবনে?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"আপুনি <xliff:g id="CARRIER">%s</xliff:g>ৰ জৰিয়তে ডেটা সংযোগ বা ইণ্টাৰনেট সংযোগ নাপাব। কেৱল ৱাই-ফাইৰ যোগেৰে ইণ্টাৰনেট উপলব্ধ হ\'ব।"</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"আপোনাৰ বাহক"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"এটা এপে অনুমতি বিচাৰি কৰা অনুৰোধ এটা ঢাকি ধৰা বাবে ছেটিঙৰ পৰা আপোনাৰ উত্তৰ সত্যাপন কৰিব পৰা নাই।"</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g>ক <xliff:g id="APP_2">%2$s</xliff:g>ৰ অংশ দেখুওৱাবলৈ অনুমতি দিবনে?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- ই <xliff:g id="APP">%1$s</xliff:g>ৰ তথ্য পঢ়িব পাৰে"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"বিবৰ্ধকৰ ৱিণ্ড’ৰ ছেটিং"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"সাধ্য সুবিধাসমূহ খুলিবলৈ টিপক। ছেটিঙত এই বুটামটো কাষ্টমাইজ অথবা সলনি কৰক।\n\n"<annotation id="link">"ছেটিং চাওক"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"বুটামটোক সাময়িকভাৱে লুকুৱাবলৈ ইয়াক একেবাৰে কাষলৈ লৈ যাওক"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"শীৰ্ষৰ বাওঁফালে নিয়ক"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"শীৰ্ষৰ সোঁফালে নিয়ক"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"তলৰ বাওঁফালে নিয়ক"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"তলৰ সোঁফালে নিয়ক"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"কাষলৈ নিয়ক আৰু লুকুৱাওক"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"কাষৰ বাহিৰলৈ নিয়ক আৰু দেখুৱাওক"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"ট’গল কৰক"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"ডিভাইচৰ নিয়ন্ত্ৰণসমূহ"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"নিয়ন্ত্ৰণসমূহ যোগ কৰিবলৈ এপ্‌ বাছনি কৰক"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"ম’বাইল ডেটা"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"সংযোজিত হৈ আছে"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"ম’বাইল ডেটা স্বয়ংক্ৰিয়ভাৱে সংযুক্ত নহ’ব"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"সংযোগ নাই"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"অন্য কোনো নেটৱৰ্ক উপলব্ধ নহয়"</string>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index 7fa603f..c5f143b 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Parlaqlıq"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Rəng inversiyası"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Rəng korreksiyası"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"İstifadəçi ayarları"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"Hazır"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Bağlayın"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"Qoşulu"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"Mobil data söndürülsün?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"<xliff:g id="CARRIER">%s</xliff:g> ilə data və ya internetə daxil ola bilməyəcəksiniz. İnternet yalnız Wi-Fi ilə əlçatan olacaq."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"operatorunuz"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"Tətbiq icazə sorğusunu gizlətdiyi üçün Ayarlar cavabınızı doğrulaya bilməz."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> tətbiqinə <xliff:g id="APP_2">%2$s</xliff:g> hissələrini göstərmək üçün icazə verilsin?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- <xliff:g id="APP">%1$s</xliff:g> tətbiqindən məlumat oxuya bilər"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Böyüdücü pəncərə ayarları"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Əlçatımlılıq funksiyalarını açmaq üçün toxunun. Ayarlarda bu düyməni fərdiləşdirin və ya dəyişdirin.\n\n"<annotation id="link">"Ayarlara baxın"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Düyməni müvəqqəti gizlətmək üçün kənara çəkin"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Yuxarıya sola köçürün"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Yuxarıya sağa köçürün"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Aşağıya sola köçürün"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Aşağıya sağa köçürün"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"İçəri keçirib gizlədin"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Kənara daşıyıb göstərin"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"keçirin"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Cihaz kontrolları"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"Kontrol əlavə etmək üçün tətbiq seçin"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobil data"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"Qoşulub"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobil data avtomatik qoşulmayacaq"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"Bağlantı yoxdur"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Heç bir başqa şəbəkə əlçatan deyil"</string>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index ffe49b2..e61b692 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Osvetljenost"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inverzija boja"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Korekcija boja"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Korisnička podešavanja"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"Gotovo"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Zatvori"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"Povezan"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"Želite da isključite mobilne podatke?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"Nećete imati pristup podacima ili internetu preko mobilnog operatera <xliff:g id="CARRIER">%s</xliff:g>. Internet će biti dostupan samo preko WiFi veze."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"mobilni operater"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"Podešavanja ne mogu da verifikuju vaš odgovor jer aplikacija skriva zahtev za dozvolu."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"Želite li da dozvolite aplikaciji <xliff:g id="APP_0">%1$s</xliff:g> da prikazuje isečke iz aplikacije <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"– Može da čita podatke iz aplikacije <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Podešavanja prozora za uvećanje"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Dodirnite za funkcije pristupačnosti. Prilagodite ili zamenite ovo dugme u Podešavanjima.\n\n"<annotation id="link">"Podešavanja"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Pomerite dugme do ivice da biste ga privremeno sakrili"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Premesti gore levo"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Premesti gore desno"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Premesti dole levo"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Premesti dole desno"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Premesti do ivice i sakrij"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Premesti izvan ivice i prikaži"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"uključite/isključite"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Kontrole uređaja"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"Odaberite aplikaciju za dodavanje kontrola"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobilni podaci"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"Povezano"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"Nije uspelo autom. povezivanje preko mob. podataka"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"Veza nije uspostavljena"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Nije dostupna nijedna druga mreža"</string>
diff --git a/packages/SystemUI/res/values-be/strings.xml b/packages/SystemUI/res/values-be/strings.xml
index 3e76985..827370f 100644
--- a/packages/SystemUI/res/values-be/strings.xml
+++ b/packages/SystemUI/res/values-be/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Яркасць"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Інверсія колераў"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Карэкцыя колераў"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Налады карыстальніка"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"Гатова"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Закрыць"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"Падлучана"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"Выключыць мабільную перадачу даных?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"У вас не будзе доступу да даных ці інтэрнэту праз аператара <xliff:g id="CARRIER">%s</xliff:g>. Інтэрнэт будзе даступны толькі праз Wi-Fi."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"ваш аператар"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"Праграма хавае запыт на дазвол, таму ваш адказ немагчыма спраўдзіць у Наладах."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"Дазволіць праграме <xliff:g id="APP_0">%1$s</xliff:g> паказваць зрэзы праграмы <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- Можа счытваць інфармацыю з праграмы <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Налады акна лупы"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Націсніце, каб адкрыць спецыяльныя магчымасці. Рэгулюйце ці замяняйце кнопку ў Наладах.\n\n"<annotation id="link">"Прагляд налад"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Каб часова схаваць кнопку, перамясціце яе на край"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Перамясціць лявей і вышэй"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Перамясціць правей і вышэй"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Перамясціць лявей і ніжэй"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Перамясціць правей і ніжэй"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Перамясціць на край і схаваць"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Перамясціць за край і паказаць"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"уключыць/выключыць"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Элементы кіравання прыладай"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"Выберыце праграму для дадавання элементаў кіравання"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"Мабільная перадача даных"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"Падключана"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"Мабільная перадача даных не ўключаецца аўтаматычна"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"Няма падключэння"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Больш няма даступных сетак"</string>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index 572e13f..880445b 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Яркост"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Цветове: инверт."</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Корекция на цветове"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Потребителски настройки"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"Готово"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Затваряне"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"Установена е връзка"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"Да се изключат ли мобилните данни?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"Няма да можете да използвате данни или интернет чрез <xliff:g id="CARRIER">%s</xliff:g>. Ще имате достъп до интернет само през Wi-Fi."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"оператора си"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"От Настройки не може да се получи потвърждение за отговора ви, защото заявката за разрешение се прикрива от приложение."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"Искате ли да разрешите на <xliff:g id="APP_0">%1$s</xliff:g> да показва части от <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"– Може да чете информация от <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Настройки за инструмента за увеличаване на прозорци"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Докоснете, за да отворите функциите за достъпност. Персон./заменете бутона от настройките.\n\n"<annotation id="link">"Преглед на настройките"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Преместете бутона до края, за да го скриете временно"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Преместване горе вляво"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Преместване горе вдясно"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Преместване долу вляво"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Преместване долу вдясно"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Преместване в края и скриване"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Преместване в края и показване"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"превключване"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Контроли за устройството"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"Изберете приложение, за да добавите контроли"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"Мобилни данни"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"Свързано"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"Връзката за мобилни данни няма да е автоматична"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"Няма връзка"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Няма други налични мрежи"</string>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index 645f6ab..c74fc69 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"উজ্জ্বলতা"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"কালার ইনভার্সন"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"রঙ সংশোধন"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"ব্যবহারকারী সেটিংস"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"সম্পন্ন হয়েছে"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"বন্ধ করুন"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"সংযুক্ত হয়েছে"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"মোবাইল ডেটা বন্ধ করবেন?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"আপনি \'<xliff:g id="CARRIER">%s</xliff:g>\'-এর মাধ্যমে ডেটা অথবা ইন্টারনেট অ্যাক্সেস করতে পারবেন না। শুধুমাত্র ওয়াই-ফাইয়ের মাধ্যমেই ইন্টারনেট অ্যাক্সেস করা যাবে।"</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"আপনার পরিষেবা প্রদানকারী"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"একটি অ্যাপ কোনও অনুমোদনের অনুরোধকে ঢেকে দিচ্ছে, তাই সেটিংস থেকে আপনার প্রতিক্রিয়া যাচাই করা যাচ্ছে না।"</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> অ্যাপটিকে <xliff:g id="APP_2">%2$s</xliff:g> এর অংশ দেখানোর অনুমতি দেবেন?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- এটি <xliff:g id="APP">%1$s</xliff:g> এর তথ্য অ্যাক্সেস করতে পারবে"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"\'ম্যাগনিফায়ার উইন্ডো\' সেটিংস"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"অ্যাক্সেসিবিলিটি ফিচার খুলতে ট্যাপ করুন। কাস্টমাইজ করুন বা সেটিংসে এই বোতামটি সরিয়ে দিন।\n\n"<annotation id="link">"সেটিংস দেখুন"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"এটি অস্থায়ীভাবে লুকাতে বোতামটি কোণে সরান"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"উপরে বাঁদিকে সরান"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"উপরে ডানদিকে সরান"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"নিচে বাঁদিকে সরান"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"নিচে ডান দিকে সরান"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"প্রান্তে যান ও আড়াল করুন"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"প্রান্ত থেকে সরান এবং দেখুন"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"টগল করুন"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"ডিভাইস কন্ট্রোল"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"কন্ট্রোল যোগ করতে অ্যাপ বেছে নিন"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"মোবাইল ডেটা"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"কানেক্ট করা আছে"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"মোবাইল ডেটা নিজে থেকে কানেক্ট হবে না"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"কানেকশন নেই"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"অন্য কোনও নেটওয়ার্ক উপলভ্য নেই"</string>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index 183dfe0..5356d74 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -248,7 +248,7 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Osvjetljenje"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inverzija boja"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Ispravka boja"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Korisničke postavke"</string>
+    <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Upravljajte korisnicima"</string>
     <string name="quick_settings_done" msgid="2163641301648855793">"Gotovo"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Zatvori"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"Povezano"</string>
@@ -727,6 +727,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"Isključiti prijenos podataka na mobilnoj mreži?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"Nećete imati pristup podacima ni internetu putem mobilnog operatera <xliff:g id="CARRIER">%s</xliff:g>. Internet će biti dostupan samo putem WiFi-ja."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"vaš operater"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"Postavke ne mogu potvrditi vaš odgovor jer aplikacija zaklanja zahtjev za odobrenje."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"Dozvoliti aplikaciji <xliff:g id="APP_0">%1$s</xliff:g> da prikazuje isječke aplikacije <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- Može čitati informacije iz aplikacije <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -785,12 +793,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Postavke prozora povećala"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Dodirnite da otvorite funkcije pristupačnosti. Prilagodite ili zamijenite dugme u Postavkama.\n\n"<annotation id="link">"Postavke"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Premjestite dugme do ivice da ga privremeno sakrijete"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Pomjeranje gore lijevo"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Pomjeranje gore desno"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Pomjeranje dolje lijevo"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Pomjeranje dolje desno"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Pomjeranje do ivice i sakrivanje"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Pomjeranje izvan ivice i prikaz"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"aktiviranje/deaktiviranje"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Kontrole uređaja"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"Odaberite aplikaciju da dodate kontrole"</string>
@@ -933,6 +947,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"Prijenos podataka na mobilnoj mreži"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"Povezano"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"Prijenos podataka se neće automatski povezati"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"Niste povezani s mrežom"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Druge mreže nisu dostupne"</string>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index daa63e6..b61ee2c 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brillantor"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversió de colors"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Correcció de color"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Configuració d\'usuari"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"Fet"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Tanca"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"Connectat"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"Vols desactivar les dades mòbils?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"No tindràs accés a dades ni a Internet mitjançant <xliff:g id="CARRIER">%s</xliff:g>. Internet només estarà disponible per Wi-Fi."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"el teu operador de telefonia mòbil"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"Com que hi ha una aplicació que oculta una sol·licitud de permís, no es pot verificar la teva resposta des de la configuració."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"Vols permetre que <xliff:g id="APP_0">%1$s</xliff:g> mostri porcions de l\'aplicació <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- Pot llegir informació de l\'aplicació <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Configuració de la finestra de la lupa"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Toca per obrir funcions d\'accessibilitat. Personalitza o substitueix el botó a Configuració.\n\n"<annotation id="link">"Mostra"</annotation>"."</string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Mou el botó a l\'extrem per amagar-lo temporalment"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Mou a dalt a l\'esquerra"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Mou a dalt a la dreta"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Mou a baix a l\'esquerra"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Mou a baix a la dreta"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Mou dins de les vores i amaga"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Mou fora de les vores i mostra"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"commuta"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Controls de dispositius"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"Selecciona l\'aplicació per afegir controls"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"Dades mòbils"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"Connectat"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"Les dades mòbils no es connectaran automàticament"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"Sense connexió"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"No hi ha cap altra xarxa disponible"</string>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index cba9448..ee36468 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Jas"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Převrácení barev"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Korekce barev"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Uživatelské nastavení"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"Hotovo"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Zavřít"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"Připojeno"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"Vypnout mobilní data?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"Prostřednictvím <xliff:g id="CARRIER">%s</xliff:g> nebudete moci používat data ani internet. Internet bude dostupný pouze přes Wi-Fi."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"vašeho operátora"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"Žádost o oprávnění je blokována jinou aplikací. Nastavení proto vaši odpověď nemůže ověřit."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"Povolit aplikaci <xliff:g id="APP_0">%1$s</xliff:g> zobrazovat ukázky z aplikace <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"– Může číst informace z aplikace <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Nastavení okna zvětšení"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Klepnutím otevřete funkce přístupnosti. Tlačítko lze upravit nebo nahradit v Nastavení.\n\n"<annotation id="link">"Nastavení"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Přesunutím tlačítka k okraji ho dočasně skryjete"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Přesunout vlevo nahoru"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Přesunout vpravo nahoru"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Přesunout vlevo dolů"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Přesunout vpravo dolů"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Přesunout k okraji a skrýt"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Přesunout okraj ven a zobrazit"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"přepnout"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Ovládání zařízení"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"Vyberte aplikaci, pro kterou chcete přidat ovládací prvky"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobilní data"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"Připojeno"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobilní data se nebudou připojovat automaticky"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"Žádné připojení"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Žádné další sítě nejsou k dispozici"</string>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index a879cbb..220014e 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -248,7 +248,7 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Lysstyrke"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Ombytning af farver"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Farvekorrigering"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Brugerindstillinger"</string>
+    <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Administrer brugere"</string>
     <string name="quick_settings_done" msgid="2163641301648855793">"Udfør"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Luk"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"Tilsluttet"</string>
@@ -727,6 +727,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"Vil du deaktivere mobildata?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"Du vil ikke have data- eller internetadgang via <xliff:g id="CARRIER">%s</xliff:g>. Der vil kun være adgang til internettet via Wi-Fi."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"dit mobilselskab"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"Indstillinger kan ikke bekræfte dit svar, da en app dækker for en anmodning om tilladelse."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"Vil du give <xliff:g id="APP_0">%1$s</xliff:g> tilladelse til at vise eksempler fra <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- Den kan læse oplysninger fra <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -785,12 +793,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Indstillinger for lupvindue"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Tryk for at åbne hjælpefunktioner. Tilpas eller erstat denne knap i Indstillinger.\n\n"<annotation id="link">"Se indstillingerne"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Flyt knappen til kanten for at skjule den midlertidigt"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Flyt op til venstre"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Flyt op til højre"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Flyt ned til venstre"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Flyt ned til højre"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Flyt ud til kanten, og skjul"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Flyt ud til kanten, og vis"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"slå til/fra"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Enhedsstyring"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"Vælg en app for at tilføje styring"</string>
@@ -933,6 +947,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobildata"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"Forbundet"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"Ingen automatisk mobildataforbindelse"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"Der er ingen forbindelse"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Der er ingen andre tilgængelige netværk"</string>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index fb135ff..0b3af67 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Helligkeit"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Farbumkehr"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Farbkorrektur"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Nutzereinstellungen"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"Fertig"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Schließen"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"Verbunden"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"Mobile Daten deaktivieren?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"Du kannst dann nicht mehr über <xliff:g id="CARRIER">%s</xliff:g> auf Daten und das Internet zugreifen. Das Internet ist nur noch über WLAN verfügbar."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"deinen Mobilfunkanbieter"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"Deine Eingabe wird von \"Einstellungen\" nicht erkannt, weil die Berechtigungsanfrage von einer App verdeckt wird."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> erlauben, Teile von <xliff:g id="APP_2">%2$s</xliff:g> anzuzeigen?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"– Darf Informationen aus <xliff:g id="APP">%1$s</xliff:g> lesen"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Einstellungen für das Vergrößerungsfenster"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Tippe, um die Bedienungshilfen aufzurufen. Du kannst diese Schaltfläche in den Einstellungen anpassen oder ersetzen.\n\n"<annotation id="link">"Zu den Einstellungen"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Durch Ziehen an den Rand wird die Schaltfläche zeitweise ausgeblendet"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Nach oben links verschieben"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Nach rechts oben verschieben"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Nach unten links verschieben"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Nach unten rechts verschieben"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"An den Rand verschieben und verbergen"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Vom Rand verschieben und anzeigen"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"Wechseln"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Gerätesteuerung"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"App zum Hinzufügen von Steuerelementen auswählen"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobile Daten"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"Verbunden"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"Keine automatische Verbindung über mobile Daten"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"Keine Verbindung"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Keine anderen Netzwerke verfügbar"</string>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index 3d15953..d707e3e 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -248,7 +248,7 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Φωτεινότητα"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Αντιστροφή χρωμάτων"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Διόρθωση χρωμάτων"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Ρυθμίσεις χρήστη"</string>
+    <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Διαχείριση χρηστών"</string>
     <string name="quick_settings_done" msgid="2163641301648855793">"Τέλος"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Κλείσιμο"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"Συνδέθηκε"</string>
@@ -727,6 +727,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"Απενεργοποίηση δεδομένων κινητής τηλεφωνίας;"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"Δεν θα έχετε πρόσβαση σε δεδομένα ή στο διαδίκτυο μέσω της εταιρείας κινητής τηλεφωνίας <xliff:g id="CARRIER">%s</xliff:g>. Θα έχετε πρόσβαση στο διαδίκτυο μόνο μέσω Wi-Fi."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"η εταιρεία κινητής τηλεφωνίας"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"Επειδή μια εφαρμογή αποκρύπτει ένα αίτημα άδειας, δεν είναι δυνατή η επαλήθευση της απάντησής σας από τις Ρυθμίσεις."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"Να επιτρέπεται στο <xliff:g id="APP_0">%1$s</xliff:g> να εμφανίζει τμήματα της εφαρμογής <xliff:g id="APP_2">%2$s</xliff:g>;"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- Μπορεί να διαβάζει πληροφορίες από την εφαρμογή <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -785,12 +793,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Ρυθμίσεις παραθύρου μεγεθυντικού φακού"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Πατήστε για άνοιγμα των λειτουργιών προσβασιμότητας. Προσαρμόστε ή αντικαταστήστε το κουμπί στις Ρυθμίσεις.\n\n"<annotation id="link">"Προβολή ρυθμίσεων"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Μετακινήστε το κουμπί στο άκρο για προσωρινή απόκρυψη"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Μετακίνηση επάνω αριστερά"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Μετακίνηση επάνω δεξιά"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Μετακίνηση κάτω αριστερά"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Μετακίνηση κάτω δεξιά"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Μετακίν. στο άκρο και απόκρυψη"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Μετακ. εκτός άκρου και εμφάν."</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"εναλλαγή"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Στοιχεία ελέγχου συσκευής"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"Επιλογή εφαρμογής για προσθήκη στοιχείων ελέγχου"</string>
@@ -933,6 +947,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"Δεδομένα κινητής τηλεφωνίας"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"Συνδέθηκε"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"Χωρίς αυτόματη σύνδεση δεδομένων κινητ. τηλεφωνίας"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"Χωρίς σύνδεση"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Δεν υπάρχουν άλλα διαθέσιμα δίκτυα"</string>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index e7166ac..3aaa5f5 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -248,7 +248,7 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brightness"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Colour inversion"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Colour correction"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"User settings"</string>
+    <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Manage users"</string>
     <string name="quick_settings_done" msgid="2163641301648855793">"Done"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Close"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"Connected"</string>
@@ -727,6 +727,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"Turn off mobile data?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"You won\'t have access to data or the Internet through <xliff:g id="CARRIER">%s</xliff:g>. Internet will only be available via Wi-Fi."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"your operator"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"Because an app is obscuring a permission request, Settings can’t verify your response."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"Allow <xliff:g id="APP_0">%1$s</xliff:g> to show <xliff:g id="APP_2">%2$s</xliff:g> slices?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"– It can read information from <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -785,12 +793,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Magnifier window settings"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Tap to open accessibility features. Customise or replace this button in Settings.\n\n"<annotation id="link">"View settings"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Move button to the edge to hide it temporarily"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Move top left"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Move top right"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Move bottom left"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Move bottom right"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Move to edge and hide"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Move out edge and show"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"toggle"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Device controls"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"Choose app to add controls"</string>
@@ -933,6 +947,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobile data"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"Connected"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobile data won\'t auto‑connect"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"No connection"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"No other networks available"</string>
diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml
index fe746d9..fb56d9f 100644
--- a/packages/SystemUI/res/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings.xml
@@ -248,7 +248,7 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brightness"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Colour inversion"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Colour correction"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"User settings"</string>
+    <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Manage users"</string>
     <string name="quick_settings_done" msgid="2163641301648855793">"Done"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Close"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"Connected"</string>
@@ -727,6 +727,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"Turn off mobile data?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"You won\'t have access to data or the Internet through <xliff:g id="CARRIER">%s</xliff:g>. Internet will only be available via Wi-Fi."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"your carrier"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"Because an app is obscuring a permission request, Settings can’t verify your response."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"Allow <xliff:g id="APP_0">%1$s</xliff:g> to show <xliff:g id="APP_2">%2$s</xliff:g> slices?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"– It can read information from <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -785,12 +793,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Magnifier window settings"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Tap to open accessibility features. Customise or replace this button in Settings.\n\n"<annotation id="link">"View settings"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Move button to the edge to hide it temporarily"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Move top left"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Move top right"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Move bottom left"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Move bottom right"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Move to edge and hide"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Move out edge and show"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"toggle"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Device controls"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"Choose app to add controls"</string>
@@ -933,6 +947,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobile data"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"Connected"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobile data won\'t auto‑connect"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"No connection"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"No other networks available"</string>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index e7166ac..3aaa5f5 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -248,7 +248,7 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brightness"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Colour inversion"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Colour correction"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"User settings"</string>
+    <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Manage users"</string>
     <string name="quick_settings_done" msgid="2163641301648855793">"Done"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Close"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"Connected"</string>
@@ -727,6 +727,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"Turn off mobile data?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"You won\'t have access to data or the Internet through <xliff:g id="CARRIER">%s</xliff:g>. Internet will only be available via Wi-Fi."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"your operator"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"Because an app is obscuring a permission request, Settings can’t verify your response."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"Allow <xliff:g id="APP_0">%1$s</xliff:g> to show <xliff:g id="APP_2">%2$s</xliff:g> slices?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"– It can read information from <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -785,12 +793,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Magnifier window settings"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Tap to open accessibility features. Customise or replace this button in Settings.\n\n"<annotation id="link">"View settings"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Move button to the edge to hide it temporarily"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Move top left"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Move top right"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Move bottom left"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Move bottom right"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Move to edge and hide"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Move out edge and show"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"toggle"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Device controls"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"Choose app to add controls"</string>
@@ -933,6 +947,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobile data"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"Connected"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobile data won\'t auto‑connect"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"No connection"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"No other networks available"</string>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index e7166ac..3aaa5f5 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -248,7 +248,7 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brightness"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Colour inversion"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Colour correction"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"User settings"</string>
+    <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Manage users"</string>
     <string name="quick_settings_done" msgid="2163641301648855793">"Done"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Close"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"Connected"</string>
@@ -727,6 +727,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"Turn off mobile data?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"You won\'t have access to data or the Internet through <xliff:g id="CARRIER">%s</xliff:g>. Internet will only be available via Wi-Fi."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"your operator"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"Because an app is obscuring a permission request, Settings can’t verify your response."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"Allow <xliff:g id="APP_0">%1$s</xliff:g> to show <xliff:g id="APP_2">%2$s</xliff:g> slices?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"– It can read information from <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -785,12 +793,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Magnifier window settings"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Tap to open accessibility features. Customise or replace this button in Settings.\n\n"<annotation id="link">"View settings"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Move button to the edge to hide it temporarily"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Move top left"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Move top right"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Move bottom left"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Move bottom right"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Move to edge and hide"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Move out edge and show"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"toggle"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Device controls"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"Choose app to add controls"</string>
@@ -933,6 +947,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobile data"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"Connected"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobile data won\'t auto‑connect"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"No connection"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"No other networks available"</string>
diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml
index b55b744..ee56838 100644
--- a/packages/SystemUI/res/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res/values-en-rXC/strings.xml
@@ -248,7 +248,7 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‎‏‎‎‎‏‏‏‏‎‏‏‏‎‎‏‎‎‏‏‏‏‎‎‏‎‎‎‏‎‏‏‎‏‏‎‎‏‏‎‏‎‏‏‎‎‏‏‎‏‎‎‎‏‎‏‎‎‎Brightness‎‏‎‎‏‎"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‎‏‎‎‏‎‏‏‏‏‏‏‎‏‏‎‎‏‏‏‎‏‏‏‏‏‎‏‎‏‏‏‏‏‏‎‎‏‏‏‎‏‏‎‏‎‎‎‏‎‏‏‎‏‎‎‎‎Color inversion‎‏‎‎‏‎"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‏‎‎‎‏‏‏‎‎‏‎‏‎‎‏‎‏‎‎‎‏‎‎‎‎‎‏‏‎‏‏‎‎‎‎‏‏‎‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‎‏‎‎‎‎Color correction‎‏‎‎‏‎"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‏‎‏‏‎‎‎‏‎‎‏‏‎‎‎‎‎‎‏‏‎‏‏‎‎‏‎‎‏‏‎‏‎‏‎‏‎‎‏‎‏‏‏‎‎‏‎‏‏‎‎‎‎‎‎‎‏‎‎User settings‎‏‎‎‏‎"</string>
+    <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‏‏‏‏‏‎‎‏‏‏‎‏‏‏‏‎‎‏‎‏‏‏‎‏‏‏‏‏‎‎‎‏‏‏‎‎‎‎‏‎‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‏‎‎Manage users‎‏‎‎‏‎"</string>
     <string name="quick_settings_done" msgid="2163641301648855793">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‎‎‎‎‎‎‏‏‎‏‏‎‎‏‏‎‎‎‏‎‎‏‏‎‎‏‏‏‎‎‏‏‎‎‎‏‎‎‏‎‎‏‏‎‏‎‎‏‎‏‏‏‏‎‎‎‏‎Done‎‏‎‎‏‎"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‎‏‏‎‏‏‎‏‏‎‎‎‏‏‎‏‏‏‏‏‎‎‏‏‎‏‎‏‏‏‎‏‏‎‎‏‏‎‎‏‎‎‎‏‎‎‎‏‏‎‎‎‎‏‎‎‎‏‎Close‎‏‎‎‏‎"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‎‏‏‏‎‎‎‎‎‏‏‏‎‎‏‏‏‏‏‎‏‎‏‏‏‏‏‏‏‏‎‏‏‎‏‏‏‏‎‏‎‏‎‎‏‏‏‎‏‏‏‎‏‎‏‎‏‏‎Connected‎‏‎‎‏‎"</string>
@@ -727,6 +727,10 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‏‎‎‏‏‏‏‎‎‏‏‎‎‎‏‏‎‏‏‎‎‏‏‎‏‏‏‏‏‎‎‎‏‏‏‏‏‎‎‏‏‎‎‎‏‏‎‎‎‎‏‏‏‏‏‏‏‎‎Turn off mobile data?‎‏‎‎‏‎"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‏‎‏‏‎‏‎‏‎‏‏‏‏‏‎‏‏‎‎‎‎‎‎‏‎‏‏‏‎‎‏‎‏‎‏‎‎‎‏‎‏‎‎‎‏‎‏‏‎‎‎‏‎‏‏‏‏‎You wont have access to data or the internet through ‎‏‎‎‏‏‎<xliff:g id="CARRIER">%s</xliff:g>‎‏‎‎‏‏‏‎. Internet will only be available via Wi-Fi.‎‏‎‎‏‎"</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‎‎‎‏‎‎‏‏‎‏‎‎‎‏‎‏‏‏‏‏‎‎‎‎‏‎‏‎‏‎‏‎‎‏‎‎‏‎‏‏‏‏‏‎‏‎‏‎‎‏‏‏‏‏‏‏‎‎your carrier‎‏‎‎‏‎"</string>
+    <string name="auto_data_switch_disable_title" msgid="5146527155665190652">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‏‏‎‏‏‎‏‏‎‎‎‎‏‎‎‎‏‏‎‎‏‎‏‏‏‎‎‎‏‏‏‏‎‎‎‏‎‏‎‎‏‏‎‎‏‏‏‏‏‎‏‏‏‏‏‏‎‎‎Switch back to ‎‏‎‎‏‏‎<xliff:g id="CARRIER">%s</xliff:g>‎‏‎‎‏‏‏‎?‎‏‎‎‏‎"</string>
+    <string name="auto_data_switch_disable_message" msgid="5885533647399535852">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎‏‏‎‏‎‏‏‎‏‏‎‎‏‏‏‎‏‏‎‏‎‏‎‏‏‎‏‎‏‏‏‏‎‏‏‎‏‏‎‏‏‏‏‎‎‎‏‎‎‏‏‏‎‏‏‎‎‎Mobile data wont automatically switch based on availability‎‏‎‎‏‎"</string>
+    <string name="auto_data_switch_dialog_negative_button" msgid="2370876875999891444">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‎‎‏‏‏‎‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‏‎‏‎‎‎‏‎‎‎‎‎‎‏‎‏‎‎‏‎‏‎‎‏‏‏‏‏‏‏‏‎‏‎‎‎No thanks‎‏‎‎‏‎"</string>
+    <string name="auto_data_switch_dialog_positive_button" msgid="8531782041263087564">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‎‏‏‎‎‏‏‎‏‏‏‏‏‎‏‎‏‎‎‏‎‎‎‎‎‎‏‎‏‏‏‎‏‎‎‎‏‎‏‏‎‎‎‏‏‏‏‏‏‏‎‎‏‏‎‎‎Yes, switch‎‏‎‎‏‎"</string>
     <string name="touch_filtered_warning" msgid="8119511393338714836">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‎‏‎‏‎‏‏‏‎‎‏‎‎‏‏‎‎‏‎‎‏‎‏‏‏‏‏‎‏‏‏‎‏‏‎‏‏‎‎‎‎‏‏‏‎‏‎‏‎‏‏‎‏‎‏‎‎‎Because an app is obscuring a permission request, Settings can’t verify your response.‎‏‎‎‏‎"</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‏‎‏‎‏‎‎‎‏‏‏‎‎‏‎‎‎‏‏‎‎‏‎‏‏‏‏‎‎‎‏‎‎‎‎‎‎‎‏‏‏‏‏‏‎‎‎‏‎‎‏‎‏‏‎‏‎‎‏‎Allow ‎‏‎‎‏‏‎<xliff:g id="APP_0">%1$s</xliff:g>‎‏‎‎‏‏‏‎ to show ‎‏‎‎‏‏‎<xliff:g id="APP_2">%2$s</xliff:g>‎‏‎‎‏‏‏‎ slices?‎‏‎‎‏‎"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‎‎‏‎‏‎‎‏‎‏‏‏‎‎‏‎‏‎‏‏‏‏‎‏‎‏‎‎‏‏‎‎‎‎‎‏‏‏‎‏‎‏‏‎‏‎‏‎‎‎‎‎‎‎‎‎‏‎‎- It can read information from ‎‏‎‎‏‏‎<xliff:g id="APP">%1$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
@@ -785,12 +789,15 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‏‏‎‏‎‏‎‏‏‎‏‏‎‏‎‎‏‏‎‎‎‎‏‏‎‏‏‏‏‎‏‎‏‏‏‏‎‏‏‏‎‎‏‏‏‏‎‏‏‏‎‎‎‏‎‎‏‎‎Magnifier window settings‎‏‎‎‏‎"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‎‎‏‏‎‎‏‏‏‎‎‎‎‏‎‏‏‎‏‏‎‎‎‏‏‎‏‎‎‏‏‎‎‎‎‏‎‎‏‏‏‏‎‎‏‏‏‏‏‎‏‎‎‏‏‏‎‎Tap to open accessibility features. Customize or replace this button in Settings.‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎\n‎‏‎‎‏‏‏‎‎‏‎‎‏‏‎"<annotation id="link">"‎‏‎‎‏‏‏‎View settings‎‏‎‎‏‏‎"</annotation>"‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‎‏‎‎‏‎‎‏‏‎‏‏‎‎‎‎‏‎‎‏‎‏‎‏‎‏‎‎‏‏‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‏‎‎‎‏‎‎‎‏‏‎‏‎Move button to the edge to hide it temporarily‎‏‎‎‏‎"</string>
+    <string name="accessibility_floating_button_undo" msgid="511112888715708241">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‏‏‏‏‎‎‎‏‎‏‏‏‏‏‎‏‎‏‏‎‎‏‏‏‎‏‏‎‏‏‎‏‏‏‎‎‎‎‎‎‎‏‏‎‎‎‎‎‎‏‏‏‎‏‎‏‎‎‎‏‎Undo‎‏‎‎‏‎"</string>
+    <string name="accessibility_floating_button_undo_message_text" msgid="3044079592757099698">"{count,plural, =1{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‎‎‎‏‏‏‏‏‎‏‎‏‏‏‏‏‎‎‏‎‎‎‎‏‎‏‏‏‎‏‎‏‎‎‏‎‎‏‏‎‏‎‏‏‏‏‏‎‎‏‎‏‏‎‎‏‎‎‎‏‎‎‏‏‎{label}‎‏‎‎‏‏‏‎ shortcut removed‎‏‎‎‏‎}other{‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‏‎‎‎‏‏‏‏‏‎‏‎‏‏‏‏‏‎‎‏‎‎‎‎‏‎‏‏‏‎‏‎‏‎‎‏‎‎‏‏‎‏‎‏‏‏‏‏‎‎‏‎‏‏‎‎‏‎‎# shortcuts removed‎‏‎‎‏‎}}"</string>
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‏‎‏‏‎‎‏‎‎‎‏‏‏‏‎‏‏‏‏‏‏‏‎‎‏‎‏‎‎‎‎‏‏‎‏‎‏‏‏‎‏‏‏‏‏‏‏‎‎‎‎‎‏‎‏‎‎‏‎Move top left‎‏‎‎‏‎"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‎‏‎‏‏‏‏‎‏‏‎‏‎‏‎‏‏‏‏‎‏‎‎‎‎‏‎‎‎‏‏‎‏‎‎‎‏‏‏‏‏‏‏‎‎‏‏‏‎‎‎‎‏‏‏‏‏‎Move top right‎‏‎‎‏‎"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‏‏‏‏‎‎‏‏‎‏‏‏‎‏‏‏‎‎‎‏‏‏‎‎‎‏‏‏‎‏‏‏‎‎‏‎‏‎‎‏‎‏‎‏‏‏‎‎‎‏‏‎‏‏‏‎‏‎Move bottom left‎‏‎‎‏‎"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‏‏‏‏‏‏‏‏‏‏‏‎‏‎‎‏‏‏‎‏‏‎‎‎‏‏‏‏‎‎‎‎‏‎‏‎‎‏‎‎‏‎‏‎‎‏‎‎‏‎‏‏‏‎‏‎‎‎Move bottom right‎‏‎‎‏‎"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‏‎‎‏‏‎‎‎‏‎‏‎‏‎‎‏‎‎‏‎‏‏‎‏‎‎‏‎‏‏‏‎‎‎‎‎‏‎‏‏‏‏‏‏‏‏‎‎‎‏‎‎‎‎‎‏‎‎Move to edge and hide‎‏‎‎‏‎"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‏‏‏‏‏‏‎‎‏‎‎‎‎‏‎‎‏‎‏‏‎‎‎‎‏‏‏‏‏‏‏‎‏‎‎‎‏‏‏‎‎‏‎‎‏‎‎‏‎‏‏‏‎‏‏‏‏‎‎Move out edge and show‎‏‎‎‏‎"</string>
+    <string name="accessibility_floating_button_action_remove_menu" msgid="6730432848162552135">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‎‏‎‏‏‎‎‏‏‏‎‏‎‎‏‏‎‏‎‎‎‎‎‏‎‎‎‎‎‏‏‎‏‏‎‏‎‏‏‎‎‏‎‏‏‏‏‎‎‏‎‏‎‎‎‏‏‏‎Remove‎‏‎‎‏‎"</string>
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‏‏‎‏‎‏‏‎‎‏‎‎‎‏‏‎‎‎‏‏‏‎‎‏‎‏‏‎‏‏‎‎‏‎‏‎‎‎‏‎‏‏‎‏‏‏‎‏‎‎‎‏‏‎‎‎‏‎‏‎toggle‎‏‎‎‏‎"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‎‏‏‏‎‏‎‎‏‎‏‏‎‎‏‎‎‎‏‏‏‏‏‏‏‎‎‎‏‏‎‎‎‎‏‎‏‏‏‏‎‏‏‎‏‏‎‎‏‏‎‎‎‎‎‎‏‎Device controls‎‏‎‎‏‎"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‏‏‏‎‏‏‏‏‎‎‏‏‏‎‏‏‏‏‏‏‎‏‏‏‎‎‎‏‎‏‎‎‎‎‏‎‎‏‏‏‎‏‎‎‏‏‏‏‏‏‎‏‎‎‎‎‎‎‎‎Choose app to add controls‎‏‎‎‏‎"</string>
@@ -933,6 +940,8 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‎‏‏‏‎‎‎‏‏‏‏‎‏‏‎‏‏‏‏‎‏‏‏‎‏‎‏‎‏‎‎‏‎‏‎‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‎‎‎‏‎‏‎Mobile data‎‏‎‎‏‎"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‏‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‏‎‎‏‎‎‏‎‏‎‎‎‏‎‎‏‏‏‏‏‎‎‏‎‎‏‎‏‎‏‎‎‎‏‎‎‏‏‎‎‎‏‎‎‏‏‎<xliff:g id="STATE">%1$s</xliff:g>‎‏‎‎‏‏‏‎ / ‎‏‎‎‏‏‎<xliff:g id="NETWORKMODE">%2$s</xliff:g>‎‏‎‎‏‏‏‎‎‏‎‎‏‎"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‏‎‏‎‎‎‏‏‎‏‏‏‎‎‎‎‎‎‎‏‎‏‎‏‎‎‏‏‎‏‏‏‎‎‎‏‎‎‏‎‏‏‏‏‎‎‎‏‎‎‏‏‏‏‏‎‎‏‏‎Connected‎‏‎‎‏‎"</string>
+    <string name="mobile_data_temp_connection_active" msgid="4590222725908806824">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‏‏‏‎‏‏‎‎‏‏‏‎‏‏‏‏‏‏‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‏‏‎‏‏‏‎‎‎‎‎‏‏‏‏‎‎‏‎‏‎‏‎‎‎‎Temporarily connected‎‏‎‎‏‎"</string>
+    <string name="mobile_data_poor_connection" msgid="819617772268371434">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‏‎‏‎‏‏‏‏‏‏‏‎‏‏‏‏‎‎‎‎‎‎‏‎‏‎‏‏‏‎‏‏‏‏‎‏‎‎‏‏‏‎‎‏‏‏‏‎‏‏‏‏‎‏‎‏‎‎Poor connection‎‏‎‎‏‎"</string>
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‏‎‏‏‎‏‏‎‎‏‎‎‏‎‎‎‎‎‎‏‏‎‏‏‎‎‎‏‏‏‎‎‏‏‎‎‏‏‎‏‏‏‎‎‎‎‎‎‎‏‎‎‏‎‎‏‏‏‎Mobile data won\'t auto‑connect‎‏‎‎‏‎"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‏‏‏‏‎‎‏‎‎‎‏‏‏‎‎‏‎‏‏‏‏‎‎‏‏‎‎‎‏‎‎‏‏‎‏‏‎‎‎‎‎‏‏‏‎‎‏‎‏‏‏‏‏‏‎‎‎‏‎No connection‎‏‎‎‏‎"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‎‏‎‏‎‏‏‏‏‏‏‎‎‎‎‏‏‏‏‎‎‏‏‏‏‎‏‏‏‎‎‎‎‏‏‏‏‎‏‏‎‎‏‏‏‏‎‏‏‎‏‎‏‎‏‎‎‎No other networks available‎‏‎‎‏‎"</string>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index e7a6cf1..6160caf 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brillo"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Invertir colores"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Corregir colores"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Configuración del usuario"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"Listo"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Cerrar"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"Conectado"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"¿Deseas desactivar los datos móviles?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"No tendrás acceso a datos móviles ni a Internet a través de <xliff:g id="CARRIER">%s</xliff:g>. Solo podrás conectarte a Internet mediante Wi‑Fi."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"tu proveedor"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"Como una app está bloqueando una solicitud de permiso, Configuración no puede verificar tu respuesta."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"¿Permitir que <xliff:g id="APP_0">%1$s</xliff:g> muestre fragmentos de <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- Puede leer información sobre <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Configuración de la ventana de ampliación"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Presiona para abrir las funciones de accesibilidad. Personaliza o cambia botón en Config.\n\n"<annotation id="link">"Ver config"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Mueve el botón hacia el borde para ocultarlo temporalmente"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Mover arriba a la izquierda"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Mover arriba a la derecha"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Mover abajo a la izquierda"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Mover abajo a la derecha"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Mover fuera de borde y ocultar"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Mover fuera de borde y mostrar"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"activar o desactivar"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Controles de dispositivos"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"Elige la app para agregar los controles"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"Datos móviles"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"Conexión establecida"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"No se conectarán automáticamente los datos móviles"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"Sin conexión"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"No hay otras redes disponibles"</string>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index e8218b0..ed9e7d9 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brillo"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Invertir colores"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Corrección de color"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Ajustes de usuario"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"Hecho"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Cerrar"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"Conectado"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"¿Desactivar datos móviles?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"No tendrás acceso a datos móviles ni a Internet a través de <xliff:g id="CARRIER">%s</xliff:g>. Solo podrás conectarte a Internet mediante Wi‑Fi."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"tu operador"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"Una aplicación impide ver una solicitud de permiso, por lo que Ajustes no puede verificar tu respuesta."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"¿Permitir que <xliff:g id="APP_0">%1$s</xliff:g> muestre fragmentos de <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- Puede leer información de <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Configuración de la ventana de la lupa"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Toca para abrir funciones de accesibilidad. Personaliza o sustituye este botón en Ajustes.\n\n"<annotation id="link">"Ver ajustes"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Mueve el botón hacia el borde para ocultarlo temporalmente"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Mover arriba a la izquierda"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Mover arriba a la derecha"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Mover abajo a la izquierda"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Mover abajo a la derecha"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Mover al borde y ocultar"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Mover al borde y mostrar"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"activar/desactivar"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Control de dispositivos"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"Elige una aplicación para añadir controles"</string>
@@ -839,7 +854,7 @@
     <string name="controls_media_seekbar_description" msgid="4389621713616214611">"<xliff:g id="ELAPSED_TIME">%1$s</xliff:g> de <xliff:g id="TOTAL_TIME">%2$s</xliff:g>"</string>
     <string name="controls_media_button_play" msgid="2705068099607410633">"Reproducir"</string>
     <string name="controls_media_button_pause" msgid="8614887780950376258">"Pausar"</string>
-    <string name="controls_media_button_prev" msgid="8126822360056482970">"Pista anterior"</string>
+    <string name="controls_media_button_prev" msgid="8126822360056482970">"Canción anterior"</string>
     <string name="controls_media_button_next" msgid="6662636627525947610">"Siguiente pista"</string>
     <string name="controls_media_button_connecting" msgid="3138354625847598095">"Conectando"</string>
     <string name="controls_media_smartspace_rec_title" msgid="1699818353932537407">"Reproducir"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"Datos móviles"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"Conectado"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"Los datos móviles no se conectarán automáticamente"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"Sin conexión"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"No hay otras redes disponibles"</string>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index 11a9dc9..417c2c2 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Heledus"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Värvide ümberpööramine"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Värviparandus"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Kasutaja seaded"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"Valmis"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Sule"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"Ühendatud"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"Kas lülitada mobiilne andmeside välja?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"Pärast seda pole teil operaatori <xliff:g id="CARRIER">%s</xliff:g> kaudu juurdepääsu andmesidele ega internetile. Internet on saadaval ainult WiFi kaudu."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"teie operaator"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"Seaded ei saa teie vastust kinnitada, sest rakendus varjab loataotlust."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"Kas lubada rakendusel <xliff:g id="APP_0">%1$s</xliff:g> näidata rakenduse <xliff:g id="APP_2">%2$s</xliff:g> lõike?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- See saab lugeda teavet rakendusest <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Luubi akna seaded"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Puudutage juurdepääsufunktsioonide avamiseks. Kohandage nuppu või asendage see seadetes.\n\n"<annotation id="link">"Kuva seaded"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Teisaldage nupp serva, et see ajutiselt peita"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Teisalda üles vasakule"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Teisalda üles paremale"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Teisalda alla vasakule"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Teisalda alla paremale"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Teisalda serva ja kuva"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Teisalda servast eemale ja kuva"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"lülita"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Seadmete juhikud"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"Valige juhtelementide lisamiseks rakendus"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobiilne andmeside"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"Ühendatud"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobiilset andmesideühendust ei looda automaatselt"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"Ühendus puudub"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Ühtegi muud võrku pole saadaval"</string>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index 3714781..ee1ba23 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Distira"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Kolore-alderantzikatzea"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Koloreen zuzenketa"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Erabiltzaile-ezarpenak"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"Eginda"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Itxi"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"Konektatuta"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"Datu-konexioa desaktibatu nahi duzu?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"<xliff:g id="CARRIER">%s</xliff:g> erabilita ezingo dituzu erabili datuak edo Internet. Wifi-sare baten bidez soilik konektatu ahal izango zara Internetera."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"Zure operadorea"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"Aplikazio bat baimen-eskaera oztopatzen ari denez, ezarpenek ezin dute egiaztatu erantzuna."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_2">%2$s</xliff:g> aplikazioaren zatiak erakusteko baimena eman nahi diozu <xliff:g id="APP_0">%1$s</xliff:g> aplikazioari?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- <xliff:g id="APP">%1$s</xliff:g> aplikazioaren informazioa irakur dezake."</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Luparen leihoaren ezarpenak"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Erabilerraztasun-eginbideak irekitzeko, sakatu hau. Ezarpenetan pertsonalizatu edo ordez dezakezu botoia.\n\n"<annotation id="link">"Ikusi ezarpenak"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Eraman botoia ertzera aldi baterako ezkutatzeko"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Eraman goialdera, ezkerretara"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Eraman goialdera, eskuinetara"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Eraman behealdera, ezkerretara"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Eraman behealdera, eskuinetara"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Eraman ertzera eta ezkutatu"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Atera ertzetik eta erakutsi"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"aldatu"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Gailuak kontrolatzeko widgetak"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"Aukeratu aplikazio bat kontrolatzeko aukerak gehitzeko"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"Datu-konexioa"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> (<xliff:g id="NETWORKMODE">%2$s</xliff:g>)"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"Konektatuta"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"Ez da automatikoki aktibatuko datu-konexioa"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"Konexiorik gabe"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Ez dago beste sare erabilgarririk"</string>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index 183a9ed..62d73ae 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"روشنایی"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"وارونگی رنگ"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"تصحیح رنگ"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"تنظیمات کاربر"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"تمام"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"بستن"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"متصل"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"داده تلفن همراه خاموش شود؟"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"‏نمی‌توانید ازطریق <xliff:g id="CARRIER">%s</xliff:g> به داده یا اینترنت دسترسی داشته باشید. اینترنت فقط ازطریق Wi-Fi در دسترس خواهد بود."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"شرکت مخابراتی شما"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"چون برنامه‌ای درحال ایجاد تداخل در درخواست مجوز است، «تنظیمات» نمی‌تواند پاسخ شما را تأیید کند."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"به <xliff:g id="APP_0">%1$s</xliff:g> اجازه داده شود تکه‌های <xliff:g id="APP_2">%2$s</xliff:g> را نشان دهد؟"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- می‌تواند اطلاعات <xliff:g id="APP">%1$s</xliff:g> را بخواند"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"تنظیمات پنجره ذره‌بین"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"برای باز کردن ویژگی‌های دسترس‌پذیری ضربه بزنید. در تنظیمات این دکمه را سفارشی یا جایگزین کنید\n\n"<annotation id="link">"تنظیمات"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"برای پنهان کردن موقتی دکمه، آن را به لبه ببرید"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"انتقال به بالا سمت راست"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"انتقال به بالا سمت چپ"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"انتقال به پایین سمت راست"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"انتقال به پایین سمت چپ"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"انتقال به لبه و پنهان کردن"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"انتقال به خارج از لبه و نمایش"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"روشن/ خاموش کردن"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"کنترل‌های دستگاه"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"انتخاب برنامه برای افزودن کنترل‌ها"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"داده تلفن همراه"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"متصل است"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"داده تلفن همراه به‌طور خودکار متصل نخواهد شد"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"اتصال برقرار نیست"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"شبکه دیگری وجود ندارد"</string>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index df05015..bfb450f 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Kirkkaus"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Käänteiset värit"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Värinkorjaus"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Käyttäjäasetukset"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"Valmis"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Sulje"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"Yhdistetty"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"Laitetaanko mobiilidata pois päältä?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"<xliff:g id="CARRIER">%s</xliff:g> ei enää tarjoa pääsyä dataan eikä internetyhteyttä, joka on saatavilla vain Wi-Fin kautta."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"operaattorisi"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"Sovellus peittää käyttöoikeuspyynnön, joten Asetukset ei voi vahvistaa valintaasi."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"Saako <xliff:g id="APP_0">%1$s</xliff:g> näyttää osia sovelluksesta <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"– Se voi lukea tietoja sovelluksesta <xliff:g id="APP">%1$s</xliff:g>."</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Ikkunan suurennuksen asetukset"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Avaa esteettömyysominaisuudet napauttamalla. Yksilöi tai vaihda painike asetuksista.\n\n"<annotation id="link">"Avaa asetukset"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Piilota painike tilapäisesti siirtämällä se reunaan"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Siirrä vasempaan yläreunaan"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Siirrä oikeaan yläreunaan"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Siirrä vasempaan alareunaan"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Siirrä oikeaan alareunaan"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Siirrä reunaan ja piilota"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Siirrä pois reunasta ja näytä"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"vaihda"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Laitehallinta"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"Valitse sovellus lisätäksesi säätimiä"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobiilidata"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"Yhdistetty"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobiilidata ei yhdisty automaattisesti"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"Ei yhteyttä"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Ei muita verkkoja käytettävissä"</string>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index c5c941b..b2d925c 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Luminosité"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversion des couleurs"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Correction des couleurs"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Paramètres utilisateur"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"Terminé"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Fermer"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"Connecté"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"Désactiver les données cellulaires?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"Vous n\'aurez pas accès aux données ni à Internet avec <xliff:g id="CARRIER">%s</xliff:g>. Vous ne pourrez accéder à Internet que par Wi-Fi."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"votre fournisseur de services"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"Une application obscurcit une demande d\'autorisation, alors Paramètres ne peut pas vérifier votre réponse."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"Autoriser <xliff:g id="APP_0">%1$s</xliff:g> à afficher <xliff:g id="APP_2">%2$s</xliff:g> tranches?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- Il peut lire de l\'information de <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Paramètres de la fenêtre de loupe"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Touchez pour ouvrir fonction. d\'access. Personnalisez ou remplacez bouton dans Param.\n\n"<annotation id="link">"Afficher param."</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Déplacez le bouton vers le bord pour le masquer temporairement"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Déplacer dans coin sup. gauche"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Déplacer dans coin sup. droit"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Déplacer dans coin inf. gauche"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Déplacer dans coin inf. droit"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Éloigner du bord et masquer"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Éloigner du bord et afficher"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"basculer"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Commandes des appareils"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"Sélectionnez l\'application pour laquelle ajouter des commandes"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"Données cellulaires"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"Connexion active"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"Aucune connexion auto. des données cellulaires"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"Aucune connexion"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Aucun autre réseau n\'est accessible"</string>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index 1a2b877..12a88c6 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Luminosité"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversion des couleurs"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Correction des couleurs"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Paramètres utilisateur"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"OK"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Fermer"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"Connecté"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"Désactiver les données mobiles ?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"Vous n\'aurez pas accès aux données mobiles ni à Internet via <xliff:g id="CARRIER">%s</xliff:g>. Vous ne pourrez accéder à Internet que par Wi-Fi."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"votre opérateur"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"L\'application Paramètres ne peut pas valider votre réponse, car une application masque la demande d\'autorisation."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"Autoriser <xliff:g id="APP_0">%1$s</xliff:g> à afficher des éléments de <xliff:g id="APP_2">%2$s</xliff:g> ?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- Accès aux informations de <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Paramètres de la fenêtre d\'agrandissement"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Appuyez pour ouvrir fonctionnalités d\'accessibilité. Personnalisez ou remplacez bouton dans paramètres.\n\n"<annotation id="link">"Voir paramètres"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Déplacer le bouton vers le bord pour le masquer temporairement"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Déplacer en haut à gauche"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Déplacer en haut à droite"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Déplacer en bas à gauche"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Déplacer en bas à droite"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Rapprocher du bord et masquer"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Éloigner du bord et afficher"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"activer/désactiver"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Commandes des appareils"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"Sélectionnez l\'appli pour laquelle ajouter des commandes"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"Données mobiles"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"Connecté"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"Pas de connexion automatique des données mobiles"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"Aucune connexion"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Aucun autre réseau disponible"</string>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index e5def1e..d18d596 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brillo"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversión da cor"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Corrección da cor"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Configuración de usuario"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"Feito"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Pechar"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"Conectado"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"Queres desactivar os datos móbiles?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"Non terás acceso aos datos nin a Internet a través de <xliff:g id="CARRIER">%s</xliff:g>. Internet só estará dispoñible mediante a wifi."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"o teu operador"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"Dado que unha aplicación se superpón sobre unha solicitude de permiso, a configuración non pode verificar a túa resposta."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"Queres permitir que <xliff:g id="APP_0">%1$s</xliff:g> mostre fragmentos de aplicación de <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- Pode ler información da aplicación <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Configuración da ventá da lupa"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Toca para abrir as funcións de accesibilidade. Cambia este botón en Configuración.\n\n"<annotation id="link">"Ver configuración"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Para ocultar temporalmente o botón, móveo ata o bordo"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Mover á parte super. esquerda"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Mover á parte superior dereita"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Mover á parte infer. esquerda"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Mover á parte inferior dereita"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Mover ao bordo e ocultar"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Mover fóra do bordo e mostrar"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"activar/desactivar"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Control de dispositivos"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"Escolle unha aplicación para engadir controis"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"Datos móbiles"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"Conectada"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"Os datos móbiles non se conectarán automaticamente"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"Sen conexión"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Non hai outras redes dispoñibles"</string>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index 552b049..034c161 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"તેજ"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"વિપરીત રંગમાં બદલવું"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"રંગ સુધારણા"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"વપરાશકર્તા સેટિંગ"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"થઈ ગયું"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"બંધ કરો"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"કનેક્ટ થયેલું"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"મોબાઇલ ડેટા બંધ કરીએ?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"તમને <xliff:g id="CARRIER">%s</xliff:g> મારફતે ડેટા અથવા ઇન્ટરનેટનો ઍક્સેસ મળશે નહીં. ઇન્ટરનેટ માત્ર વાઇ-ફાઇ દ્વારા ઉપલબ્ધ થશે."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"તમારા કૅરિઅર"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"કોઈ ઍપ પરવાનગી વિનંતીને અસ્પષ્ટ કરતી હોવાને કારણે, સેટિંગ તમારા પ્રતિસાદને ચકાસી શકતું નથી."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g>ને <xliff:g id="APP_2">%2$s</xliff:g> સ્લાઇસ બતાવવાની મંજૂરી આપીએ?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- મારાથી <xliff:g id="APP">%1$s</xliff:g>ની માહિતી વાંચી શકાતી નથી"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"મેગ્નિફાયર વિન્ડોના સેટિંગ"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"ઍક્સેસિબિલિટી સુવિધાઓ ખોલવા માટે ટૅપ કરો. સેટિંગમાં આ બટનને કસ્ટમાઇઝ કરો અથવા બદલો.\n\n"<annotation id="link">"સેટિંગ જુઓ"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"તેને હંગામી રૂપે ખસેડવા માટે બટનને કિનારી પર ખસેડો"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ઉપર ડાબે ખસેડો"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"ઉપર જમણે ખસેડો"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"નીચે ડાબે ખસેડો"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"નીચે જમણે ખસેડો"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"કિનારી પર ખસેડો અને છુપાવો"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"કિનારીથી ખસેડો અને બતાવો"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"ટૉગલ કરો"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"ડિવાઇસનાં નિયંત્રણો"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"નિયંત્રણો ઉમેરવા માટે ઍપ પસંદ કરો"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"મોબાઇલ ડેટા"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"કનેક્ટ કરેલું"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"મોબાઇલ ડેટા ઑટોમૅટિક રીતે કનેક્ટ થશે નહીં"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"કોઈ કનેક્શન નથી"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"બીજાં કોઈ નેટવર્ક ઉપલબ્ધ નથી"</string>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index c0052d6..dc94a8d 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"स्क्रीन की रोशनी"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"रंग बदलने की सुविधा"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"रंग में सुधार करने की सुविधा"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"उपयोगकर्ता सेटिंग"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"हो गया"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"रद्द करें"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"कनेक्ट है"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"मोबाइल डेटा बंद करना चाहते हैं?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"आप <xliff:g id="CARRIER">%s</xliff:g> से डेटा या इंटरनेट का इस्तेमाल नहीं कर पाएंगे. इंटरनेट सिर्फ़ वाई-फ़ाई से चलेगा."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"आपको मोबाइल और इंटरनेट सेवा देने वाली कंपनी"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"ऐप की वजह से मंज़ूरी के अनुरोध को समझने में दिक्कत हो रही है, इसलिए सेटिंग से आपके जवाब की पुष्टि नहीं हो पा रही है."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> को <xliff:g id="APP_2">%2$s</xliff:g> के हिस्से (स्लाइस) दिखाने की मंज़ूरी दें?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- यह <xliff:g id="APP">%1$s</xliff:g> से सूचना पढ़ सकता है"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"ज़ूम करने की सुविधा वाली विंडो से जुड़ी सेटिंग"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"सुलभता सुविधाएं खोलने के लिए टैप करें. सेटिंग में, इस बटन को बदलें या अपने हिसाब से सेट करें.\n\n"<annotation id="link">"सेटिंग देखें"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"बटन को कुछ समय छिपाने के लिए, उसे किनारे पर ले जाएं"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"सबसे ऊपर बाईं ओर ले जाएं"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"सबसे ऊपर दाईं ओर ले जाएं"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"सबसे नीचे बाईं ओर ले जाएं"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"सबसे नीचे दाईं ओर ले जाएं"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"एज पर ले जाएं और छिपाएं"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"एज से निकालें और दिखाएं"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"टॉगल करें"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"डिवाइस कंट्रोल"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"कंट्रोल जोड़ने के लिए ऐप्लिकेशन चुनें"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"मोबाइल डेटा"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"कनेक्ट हो गया"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"मोबाइल डेटा अपने-आप कनेक्ट नहीं होगा"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"इंटरनेट कनेक्शन नहीं है"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"कोई दूसरा नेटवर्क उपलब्ध नहीं है"</string>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index 60f0c8a..be5253f 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -248,7 +248,7 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Svjetlina"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inverzija boja"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Korekcija boja"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Korisničke postavke"</string>
+    <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Upravljajte korisnicima"</string>
     <string name="quick_settings_done" msgid="2163641301648855793">"Gotovo"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Zatvori"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"Povezano"</string>
@@ -727,6 +727,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"Isključiti mobilne podatke?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"Nećete imati pristup mobilnim podacima ili internetu putem operatera <xliff:g id="CARRIER">%s</xliff:g>. Internet će biti dostupan samo putem Wi-Fija."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"vaš mobilni operater"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"Budući da aplikacija prekriva zahtjev za dopuštenje, Postavke ne mogu potvrditi vaš odgovor."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"Želite li dopustiti aplikaciji <xliff:g id="APP_0">%1$s</xliff:g> da prikazuje isječke aplikacije <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"– može čitati informacije aplikacije <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -785,12 +793,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Postavke prozora povećala"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Dodirnite za otvaranje značajki pristupačnosti. Prilagodite ili zamijenite taj gumb u postavkama.\n\n"<annotation id="link">"Pregledajte postavke"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Pomaknite gumb do ruba da biste ga privremeno sakrili"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Premjesti u gornji lijevi kut"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Premjesti u gornji desni kut"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Premjesti u donji lijevi kut"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Premjesti u donji desni kut"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Premjesti na rub i sakrij"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Ukloni s ruba i prikaži"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"promijeni"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Kontrole uređaja"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"Odabir aplikacije za dodavanje kontrola"</string>
@@ -933,6 +947,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobilni podaci"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"Povezano"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobilna veza neće se automatski uspostaviti"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"Niste povezani"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Nije dostupna nijedna druga mreža"</string>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index 0f6daa2..cf07438 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Fényerő"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Színek invertálása"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Színjavítás"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Felhasználói beállítások"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"Kész"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Bezárás"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"Csatlakoztatva"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"Kikapcsolja a mobiladatokat?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"Nem lesz adat-, illetve internet-hozzáférése a <xliff:g id="CARRIER">%s</xliff:g> szolgáltatón keresztül. Az internethez csak Wi-Fi-n keresztül csatlakozhat."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"saját mobilszolgáltató"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"Mivel az egyik alkalmazás eltakarja az engedélykérést, a Beállítások alkalmazás nem tudja ellenőrizni az Ön válaszát."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"Engedélyezi a(z) <xliff:g id="APP_0">%1$s</xliff:g> alkalmazásnak, hogy részleteket mutasson a(z) <xliff:g id="APP_2">%2$s</xliff:g> alkalmazásból?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"– Információkat olvashat a(z) <xliff:g id="APP">%1$s</xliff:g> alkalmazásból"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Nagyítóablak beállításai"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Koppintson a kisegítő lehetőségek megnyitásához. A gombot a Beállításokban módosíthatja.\n\n"<annotation id="link">"Beállítások"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"A gombot a szélre áthelyezve ideiglenesen elrejtheti"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Áthelyezés fel és balra"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Áthelyezés fel és jobbra"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Áthelyezés le és balra"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Áthelyezés le és jobbra"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Áthelyezés a szélen kívül és elrejtés"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Áthelyezés a szélen kívül és mutatás"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"váltás"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Eszközvezérlők"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"Válasszon alkalmazást a vezérlők hozzáadásához"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobiladat"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="NETWORKMODE">%2$s</xliff:g>/<xliff:g id="STATE">%1$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"Csatlakozva"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"Nincs automatikus mobiladat-kapcsolat"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"Nincs kapcsolat"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Nincs több rendelkezésre álló hálózat"</string>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index 4724d5c..bfd051b 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Պայծառություն"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Գունաշրջում"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Գունաշտկում"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Օգտատիրոջ կարգավորումներ"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"Պատրաստ է"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Փակել"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"Միացված է"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"Անջատե՞լ բջջային ինտերնետը"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"<xliff:g id="CARRIER">%s</xliff:g> օպերատորի բջջային ինտերնետը հասանելի չի լինի: Համացանցից կկարողանաք  օգտվել միայն Wi-Fi-ի միջոցով:"</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"Ձեր"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"Քանի որ ներածումն արգելափակված է ինչ-որ հավելվածի կողմից, Կարգավորումները չեն կարող հաստատել ձեր պատասխանը:"</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"Թույլատրե՞լ <xliff:g id="APP_0">%1$s</xliff:g> հավելվածին ցուցադրել հատվածներ <xliff:g id="APP_2">%2$s</xliff:g> հավելվածից"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- Կարող է կարդալ տեղեկություններ <xliff:g id="APP">%1$s</xliff:g> հավելվածից"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Խոշորացույցի պատուհանի կարգավորումներ"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Հատուկ գործառույթները բացելու համար հպեք։ Անհատականացրեք այս կոճակը կարգավորումներում։\n\n"<annotation id="link">"Կարգավորումներ"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Կոճակը ժամանակավորապես թաքցնելու համար այն տեղափոխեք էկրանի եզր"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Տեղափոխել վերև՝ ձախ"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Տեղափոխել վերև՝ աջ"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Տեղափոխել ներքև՝ ձախ"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Տեղափոխել ներքև՝ աջ"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Տեղափոխել եզրից դուրս և թաքցնել"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Տեղափոխել եզրից դուրս և ցուցադրել"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"միացնել/անջատել"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Սարքերի կառավարման տարրեր"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"Ընտրեք հավելված` կառավարման տարրեր ավելացնելու համար"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"Բջջային ինտերնետ"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"Միացած է"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"Բջջային ինտերնետն ավտոմատ չի միանա"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"Կապ չկա"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Այլ հասանելի ցանցեր չկան"</string>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index 8fefe40..87496cf 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Kecerahan"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversi warna"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Koreksi warna"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Setelan pengguna"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"Selesai"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Tutup"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"Terhubung"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"Nonaktifkan data seluler?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"Anda tidak akan dapat mengakses data atau internet melalui <xliff:g id="CARRIER">%s</xliff:g>. Internet hanya akan tersedia melalui Wi-Fi."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"Operator Seluler Anda"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"Karena sebuah aplikasi menghalangi permintaan izin, Setelan tidak dapat memverifikasi respons Anda."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"Izinkan <xliff:g id="APP_0">%1$s</xliff:g> menampilkan potongan <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- Dapat membaca informasi dari <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Setelan jendela kaca pembesar"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Ketuk untuk membuka fitur aksesibilitas. Sesuaikan atau ganti tombol ini di Setelan.\n\n"<annotation id="link">"Lihat setelan"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Pindahkan tombol ke tepi agar tersembunyi untuk sementara"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Pindahkan ke kiri atas"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Pindahkan ke kanan atas"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Pindahkan ke kiri bawah"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Pindahkan ke kanan bawah"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Pindahkan ke tepi dan sembunyikan"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Pindahkan dari tepi dan tampilkan"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"alihkan"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Kontrol perangkat"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"Pilih aplikasi untuk menambahkan kontrol"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"Data seluler"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"Terhubung"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"Data seluler tidak akan terhubung otomatis"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"Tidak ada koneksi"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Jaringan lain tidak tersedia"</string>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index d22680e..3d7e2ea 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Birtustig"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Umsnúningur lita"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Litaleiðrétting"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Notandastillingar"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"Lokið"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Loka"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"Tengt"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"Viltu slökkva á farsímagögnum?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"Þú munt ekki hafa aðgang að gögnum eða internetinu í gegnum <xliff:g id="CARRIER">%s</xliff:g>. Aðeins verður hægt að tengjast internetinu með Wi-Fi."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"símafyrirtækið þitt"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"Stillingar geta ekki staðfest svarið þitt vegna þess að forrit er að fela heimildarbeiðni."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"Viltu leyfa <xliff:g id="APP_0">%1$s</xliff:g> að sýna sneiðar úr <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- Það getur lesið upplýsingar úr <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Stillingar stækkunarglugga"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Ýttu til að opna aðgengiseiginleika. Sérsníddu eða skiptu hnappinum út í stillingum.\n\n"<annotation id="link">"Skoða stillingar"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Færðu hnappinn að brúninni til að fela hann tímabundið"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Færa efst til vinstri"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Færa efst til hægri"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Færa neðst til vinstri"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Færa neðst til hægri"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Færa að jaðri og fela"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Færa að jaðri og birta"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"kveikja/slökkva"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Tækjastjórnun"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"Veldu forrit til að bæta við stýringum"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"Farsímagögn"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"Tengt"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"Farsímagögn tengjast ekki sjálfkrafa"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"Engin tenging"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Engin önnur net í boði"</string>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index df6c14f..1115680 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Luminosità"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversione dei colori"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Correzione del colore"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Impostazioni utente"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"Fine"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Chiudi"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"Connesso"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"Disattivare i dati mobili?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"Non avrai accesso ai dati o a Internet tramite <xliff:g id="CARRIER">%s</xliff:g>. Internet sarà disponibile soltanto tramite Wi-Fi."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"il tuo operatore"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"Un\'app sta oscurando una richiesta di autorizzazione, pertanto Impostazioni non può verificare la tua risposta."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"Vuoi consentire all\'app <xliff:g id="APP_0">%1$s</xliff:g> di mostrare porzioni dell\'app <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- Può leggere informazioni dell\'app <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Impostazioni della finestra di ingrandimento"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Tocca per aprire funzioni di accessibilità. Personalizza o sostituisci il pulsante in Impostazioni.\n\n"<annotation id="link">"Vedi impostazioni"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Sposta il pulsante fino al bordo per nasconderlo temporaneamente"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Sposta in alto a sinistra"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Sposta in alto a destra"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Sposta in basso a sinistra"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Sposta in basso a destra"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Sposta fino a bordo e nascondi"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Sposta fuori da bordo e mostra"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"attiva/disattiva"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Controllo dispositivi"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"Scegli un\'app per aggiungere controlli"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"Dati mobili"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"Connessione attiva"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"Nessuna connessione dati mobili automatica"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"Nessuna connessione"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Nessun\'altra rete disponibile"</string>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index f24964a..1fe4e3e 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -248,7 +248,7 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"בהירות"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"היפוך צבעים"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"תיקון צבע"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"הגדרות המשתמש"</string>
+    <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"ניהול משתמשים"</string>
     <string name="quick_settings_done" msgid="2163641301648855793">"סיום"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"סגירה"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"מחובר"</string>
@@ -727,6 +727,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"לכבות את חבילת הגלישה?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"‏לא תהיה לך גישה לנתונים או לאינטרנט באמצעות <xliff:g id="CARRIER">%s</xliff:g>. אינטרנט יהיה זמין רק באמצעות Wi-Fi."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"הספק שלך"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"יש אפליקציה שמסתירה את בקשת ההרשאה, ולכן אין אפשרות לאמת את התשובה בהגדרות."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"האם לאפשר ל-<xliff:g id="APP_0">%1$s</xliff:g> להציג חלקים מ-<xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- תהיה לה אפשרות לקרוא מידע מאפליקציית <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -785,12 +793,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"ההגדרות של חלון ההגדלה"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"מקישים כדי לפתוח את תכונות הנגישות. אפשר להחליף את הלחצן או להתאים אותו אישית בהגדרות.\n\n"<annotation id="link">"הצגת ההגדרות"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"כדי להסתיר זמנית את הלחצן, יש להזיז אותו לקצה"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"העברה לפינה השמאלית העליונה"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"העברה לפינה הימנית העליונה"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"העברה לפינה השמאלית התחתונה"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"העברה לפינה הימנית התחתונה"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"העברה לשוליים והסתרה"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"העברה מהשוליים והצגה"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"החלפת מצב"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"פקדי מכשירים"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"יש לבחור אפליקציה כדי להוסיף פקדים"</string>
@@ -933,6 +947,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"חבילת גלישה"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"מחובר"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"החיבור לנתונים סלולריים לא מתבצע באופן אוטומטי"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"אין חיבור"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"אין רשתות זמינות אחרות"</string>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index 7ea7223..b00ceb4 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"画面の明るさ"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"色反転"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"色補正"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"ユーザー設定"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"完了"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"閉じる"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"接続済み"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"モバイルデータを OFF にしますか?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"<xliff:g id="CARRIER">%s</xliff:g>でデータやインターネットにアクセスできなくなります。インターネットには Wi-Fi からのみ接続できます。"</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"携帯通信会社"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"アプリが許可リクエストを隠しているため、設定側でユーザーの応答を確認できません。"</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"「<xliff:g id="APP_2">%2$s</xliff:g>」のスライスの表示を「<xliff:g id="APP_0">%1$s</xliff:g>」に許可しますか?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- 「<xliff:g id="APP">%1$s</xliff:g>」からの情報の読み取り"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"拡大鏡ウィンドウの設定"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"タップしてユーザー補助機能を開きます。ボタンのカスタマイズや入れ替えを [設定] で行えます。\n\n"<annotation id="link">"設定を表示"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"ボタンを一時的に非表示にするには、端に移動させてください"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"左上に移動"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"右上に移動"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"左下に移動"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"右下に移動"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"端に移動して非表示"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"端から移動して表示"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"切り替え"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"デバイス コントロール"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"コントロールを追加するアプリの選択"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"モバイルデータ"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"接続済み"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"モバイルデータには自動接続しません"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"接続なし"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"利用できるネットワークはありません"</string>
diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml
index 93c9d94..aab9a32 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"განათება"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"ფერთა ინვერსია"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"ფერთა კორექცია"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"მომხმარებლის პარამეტრები"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"დასრულდა"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"დახურვა"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"დაკავშირებულია"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"გსურთ მობილური ინტერნეტის გამორთვა?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"თქვენ არ გექნებათ მობილურ ინტერნეტზე ან ზოგადად ინტერნეტზე წვდომა <xliff:g id="CARRIER">%s</xliff:g>-ის მეშვეობით. ინტერნეტი მხოლოდ Wi-Fi-კავშირის მეშვეობით იქნება ხელმისაწვდომი."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"თქვენი ოპერატორი"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"ვინაიდან აპი ფარავს ნებართვის მოთხოვნას, პარამეტრების მიერ თქვენი პასუხი ვერ დასტურდება."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"ანიჭებთ ნებართვას <xliff:g id="APP_0">%1$s</xliff:g>-ს, აჩვენოს <xliff:g id="APP_2">%2$s</xliff:g>-ის ფრაგმენტები?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- მას შეუძლია ინფორმაციის <xliff:g id="APP">%1$s</xliff:g>-დან წაკითხვა"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"გადიდების ფანჯრის პარამეტრები"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"შეეხეთ მარტივი წვდომის ფუნქციების გასახსნელად. მოარგეთ ან შეცვალეთ ეს ღილაკი პარამეტრებში.\n\n"<annotation id="link">"პარამეტრების ნახვა"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"გადაიტანეთ ღილაკი კიდეში, რათა დროებით დამალოთ ის"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ზევით და მარცხნივ გადატანა"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"ზევით და მარჯვნივ გადატანა"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"ქვევით და მარცხნივ გადატანა"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"ქვემოთ და მარჯვნივ გადატანა"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"კიდეში გადატანა და დამალვა"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"კიდეში გადატანა და გამოჩენა"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"გადართვა"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"მოწყობილ. მართვის საშუალებები"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"აირჩიეთ აპი მართვის საშუალებების დასამატებლად"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"მობილური ინტერნეტი"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"დაკავშირებული"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"მობილურ ინტერნეტს ავტომატურად არ დაუკავშირდება"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"კავშირი არ არის"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"სხვა ქსელები მიუწვდომელია"</string>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index e096b7e..19bfdf5 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Жарықтығы"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Түс инверсиясы"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Түсті түзету"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Пайдаланушы параметрлері"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"Дайын"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Жабу"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"Қосылды"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"Мобильдік интернет өшірілсін бе?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"<xliff:g id="CARRIER">%s</xliff:g> операторы арқылы деректерге немесе интернетке кіре алмайсыз. Интернетке тек Wi-Fi арқылы кіресіз."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"операторыңыз"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"Басқа қолданба рұқсат сұрауын жасырып тұрғандықтан, параметрлер жауабыңызды растай алмайды."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> қолданбасына <xliff:g id="APP_2">%2$s</xliff:g> қолданбасының үзінділерін көрсетуге рұқсат берілсін бе?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- Бұл <xliff:g id="APP">%1$s</xliff:g> қолданбасындағы ақпаратты оқи алады"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Ұлғайтқыш терезесінің параметрлері"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Арнайы мүмкіндікті ашу үшін түртіңіз. Түймені параметрден реттеңіз не ауыстырыңыз.\n\n"<annotation id="link">"Параметрді көру"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Түймені уақытша жасыру үшін оны шетке қарай жылжытыңыз."</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Жоғарғы сол жаққа жылжыту"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Жоғарғы оң жаққа жылжыту"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Төменгі сол жаққа жылжыту"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Төменгі оң жаққа жылжыту"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Шетке жылжыту және жасыру"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Шетке жылжыту және көрсету"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"ауыстыру"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Құрылғыны басқару элементтері"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"Басқару элементтері қосылатын қолданбаны таңдаңыз"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"Мобильдік интернет"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"Жалғанды"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"Мобильдік интернет автоматты түрде қосылмайды."</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"Байланыс жоқ"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Басқа қолжетімді желі жоқ"</string>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index aa641e4..186244b 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ពន្លឺ"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"ការបញ្ច្រាស​ពណ៌"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"ការ​កែតម្រូវ​ពណ៌"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"ការកំណត់អ្នកប្រើប្រាស់"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"រួចរាល់"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"បិទ"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"បាន​ភ្ជាប់"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"បិទទិន្នន័យទូរសព្ទចល័ត?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"អ្នកនឹង​មិន​មាន​សិទ្ធិ​ចូល​ប្រើទិន្នន័យ​ ឬអ៊ីនធឺណិត​តាមរយៈ <xliff:g id="CARRIER">%s</xliff:g> បានឡើយ។ អ៊ីនធឺណិត​នឹងអាច​ប្រើបាន​តាមរយៈ Wi-Fi តែប៉ុណ្ណោះ។"</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"ក្រុមហ៊ុន​​សេវាទូរសព្ទរបស់អ្នក"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"ការកំណត់​មិនអាច​ផ្ទៀងផ្ទាត់​ការឆ្លើយតប​របស់អ្នក​បាន​ទេ ដោយសារ​កម្មវិធី​កំពុង​បាំងសំណើ​សុំការ​អនុញ្ញាត។"</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"អនុញ្ញាតឱ្យ <xliff:g id="APP_0">%1$s</xliff:g> បង្ហាញ​ស្ថិតិប្រើប្រាស់​របស់ <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- វា​អាច​អាន​ព័ត៌មាន​ពី <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"ការកំណត់វិនដូ​កម្មវិធីពង្រីក"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"ចុចដើម្បីបើក​មុខងារ​ភាពងាយស្រួល។ ប្ដូរ ឬប្ដូរ​ប៊ូតុងនេះ​តាមបំណង​នៅក្នុង​ការកំណត់។\n\n"<annotation id="link">"មើល​ការកំណត់"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"ផ្លាស់ទី​ប៊ូតុង​ទៅគែម ដើម្បីលាក់វា​ជាបណ្ដោះអាសន្ន"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ផ្លាស់ទីទៅខាងលើផ្នែកខាងឆ្វេង"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"ផ្លាស់ទីទៅខាងលើផ្នែកខាងស្ដាំ"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"ផ្លាស់ទីទៅខាងក្រោមផ្នែកខាងឆ្វេង​"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"ផ្លាស់ទីទៅខាងក្រោមផ្នែកខាងស្ដាំ"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"ផ្លាស់ទីទៅផ្នែកខាងចុង រួចលាក់"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"ផ្លាស់ទីចេញពីផ្នែកខាងចុង រួចបង្ហាញ"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"បិទ/បើក"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"ផ្ទាំងគ្រប់គ្រងឧបករណ៍"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"ជ្រើសរើស​កម្មវិធីដែលត្រូវបញ្ចូល​ផ្ទាំងគ្រប់គ្រង"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"ទិន្នន័យ​ទូរសព្ទចល័ត"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"បានភ្ជាប់"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"ទិន្នន័យទូរសព្ទចល័ត​នឹងមិនភ្ជាប់ដោយស្វ័យប្រវត្តិទេ"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"មិនមាន​ការតភ្ជាប់ទេ"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"មិន​មាន​បណ្ដាញផ្សេងទៀតដែល​អាច​ប្រើ​បានទេ"</string>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index ab3f379..3341f8d 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ಪ್ರಕಾಶಮಾನ"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"ಕಲರ್ ಇನ್‍ವರ್ಶನ್"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"ಬಣ್ಣದ ತಿದ್ದುಪಡಿ"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"ಬಳಕೆದಾರರ ಸೆಟ್ಟಿಂಗ್‌ಗಳು"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"ಮುಗಿದಿದೆ"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"ಮುಚ್ಚಿರಿ"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"ಸಂಪರ್ಕಗೊಂಡಿದೆ"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"ಮೊಬೈಲ್ ಡೇಟಾ ಆಫ್ ಮಾಡಬೇಕೆ?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"ನೀವು <xliff:g id="CARRIER">%s</xliff:g> ಮೂಲಕ ಡೇಟಾ ಅಥವಾ ಇಂಟರ್ನೆಟ್‌ಗೆ ಪ್ರವೇಶವನ್ನು ಹೊಂದಿರುವುದಿಲ್ಲ. ಇಂಟರ್ನೆಟ್, ವೈ-ಫೈ ಮೂಲಕ ಮಾತ್ರ ಲಭ್ಯವಿರುತ್ತದೆ."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"ನಿಮ್ಮ ವಾಹಕ"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"ಅನುಮತಿ ವಿನಂತಿಯನ್ನು ಅಪ್ಲಿಕೇಶನ್ ಮರೆಮಾಚುತ್ತಿರುವ ಕಾರಣ, ಸೆಟ್ಟಿಂಗ್‌ಗಳಿಗೆ ನಿಮ್ಮ ಪ್ರತಿಕ್ರಿಯೆಯನ್ನು ಪರಿಶೀಲಿಸಲು ಸಾಧ್ಯವಿಲ್ಲ."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_2">%2$s</xliff:g> ಸ್ಲೈಸ್‌ಗಳನ್ನು ತೋರಿಸಲು <xliff:g id="APP_0">%1$s</xliff:g> ಅನ್ನು ಅನುಮತಿಸುವುದೇ?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- ಇದು <xliff:g id="APP">%1$s</xliff:g> ನಿಂದ ಮಾಹಿತಿಯನ್ನು ಓದಬಹುದು"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"ಮ್ಯಾಗ್ನಿಫೈರ್ ವಿಂಡೋ ಸೆಟ್ಟಿಂಗ್‌ಗಳು"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"ಪ್ರವೇಶಿಸುವಿಕೆ ವೈಶಿಷ್ಟ್ಯಗಳನ್ನು ತೆರೆಯಲು ಟ್ಯಾಪ್ ಮಾಡಿ. ಸೆಟ್ಟಿಂಗ್‌ಗಳಲ್ಲಿ ಈ ಬಟನ್ ಅನ್ನು ಕಸ್ಟಮೈಸ್ ಮಾಡಿ ಅಥವಾ ಬದಲಾಯಿಸಿ.\n\n"<annotation id="link">"ಸೆಟ್ಟಿಂಗ್‌ಗಳನ್ನು ವೀಕ್ಷಿಸಿ"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"ಅದನ್ನು ತಾತ್ಕಾಲಿಕವಾಗಿ ಮರೆಮಾಡಲು ಅಂಚಿಗೆ ಬಟನ್ ಸರಿಸಿ"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ಎಡ ಮೇಲ್ಭಾಗಕ್ಕೆ ಸರಿಸಿ"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"ಬಲ ಮೇಲ್ಭಾಗಕ್ಕೆ ಸರಿಸಿ"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"ಸ್ಕ್ರೀನ್‌ನ ಎಡ ಕೆಳಭಾಗಕ್ಕೆ ಸರಿಸಿ"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"ಕೆಳಗಿನ ಬಲಭಾಗಕ್ಕೆ ಸರಿಸಿ"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"ಅಂಚಿಗೆ ಸರಿಸಿ ಮತ್ತು ಮರೆಮಾಡಿ"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"ಅಂಚನ್ನು ಸರಿಸಿ ಮತ್ತು ತೋರಿಸಿ"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"ಟಾಗಲ್ ಮಾಡಿ"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"ಸಾಧನ ನಿಯಂತ್ರಣಗಳು"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"ನಿಯಂತ್ರಣಗಳನ್ನು ಸೇರಿಸಲು ಆ್ಯಪ್ ಅನ್ನು ಆಯ್ಕೆಮಾಡಿ"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"ಮೊಬೈಲ್ ಡೇಟಾ"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"ಕನೆಕ್ಟ್ ಆಗಿದೆ"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"ಮೊಬೈಲ್ ಡೇಟಾ ಸ್ವಯಂಚಾಲಿತವಾಗಿ ಕನೆಕ್ಟ್ ಆಗುವುದಿಲ್ಲ"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"ಯಾವುದೇ ಕನೆಕ್ಷನ್ ಇಲ್ಲ"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"ಇತರ ಯಾವುದೇ ನೆಟ್‌ವರ್ಕ್‌ಗಳು ಲಭ್ಯವಿಲ್ಲ"</string>
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index 11da089..783365c 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"밝기"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"색상 반전"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"색상 보정"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"사용자 설정"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"완료"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"닫기"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"연결됨"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"모바일 데이터를 사용 중지하시겠습니까?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"<xliff:g id="CARRIER">%s</xliff:g>을(를) 통해 데이터 또는 인터넷에 액세스할 수 없게 됩니다. 인터넷은 Wi-Fi를 통해서만 사용할 수 있습니다."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"이동통신사"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"앱이 권한 요청을 가리고 있기 때문에 설정에서 내 응답을 확인할 수 없습니다."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g>에서 <xliff:g id="APP_2">%2$s</xliff:g>의 슬라이스를 표시하도록 허용하시겠습니까?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- <xliff:g id="APP">%1$s</xliff:g>의 정보를 읽을 수 있음"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"돋보기 창 설정"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"접근성 기능을 열려면 탭하세요. 설정에서 이 버튼을 맞춤설정하거나 교체할 수 있습니다.\n\n"<annotation id="link">"설정 보기"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"버튼을 가장자리로 옮겨서 일시적으로 숨기세요."</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"왼쪽 상단으로 이동"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"오른쪽 상단으로 이동"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"왼쪽 하단으로 이동"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"오른쪽 하단으로 이동"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"가장자리로 옮겨서 숨기기"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"가장자리 바깥으로 옮겨서 표시"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"전환"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"기기 컨트롤"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"컨트롤을 추가할 앱을 선택하세요"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"모바일 데이터"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"연결됨"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"모바일 데이터가 자동으로 연결되지 않음"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"연결되지 않음"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"사용 가능한 다른 네트워크가 없음"</string>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index 676076c..b9da9f0 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Жарыктыгы"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Түстөрдү инверсиялоо"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Түстөрдү тууралоо"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Колдонуучунун параметрлери"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"Бүттү"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Жабуу"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"Туташкан"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"Мобилдик Интернетти өчүрөсүзбү?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"<xliff:g id="CARRIER">%s</xliff:g> байланыш оператору аркылуу Интернетке кире албай каласыз. Интернетке Wi-Fi аркылуу гана кирүүгө болот."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"байланыш операторуңуз"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"Уруксат берүү сурамыңыз көрүнбөй калгандыктан, Жөндөөлөр жообуңузду ырастай албай жатат."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> колдонмосуна <xliff:g id="APP_2">%2$s</xliff:g> үлгүлөрүн көрсөтүүгө уруксат берилсинби?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- <xliff:g id="APP">%1$s</xliff:g> колдонмосунун маалыматын окуйт"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Чоңойткуч терезесинин параметрлери"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Атайын мүмкүнчүлүктөрдү ачуу үчүн басыңыз. Бул баскычты Жөндөөлөрдөн өзгөртүңүз.\n\n"<annotation id="link">"Жөндөөлөрдү көрүү"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Баскычты убактылуу жашыра туруу үчүн экрандын четине жылдырыңыз"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Жогорку сол жакка жылдыруу"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Жогорку оң жакка жылдырыңыз"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Төмөнкү сол жакка жылдыруу"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Төмөнкү оң жакка жылдырыңыз"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Ичине жылдырып, көрсөтүңүз"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Сыртка жылдырып, көрсөтүңүз"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"өчүрүү/күйгүзүү"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Түзмөктү башкаруу элементтери"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"Башкаруу элементтери кошула турган колдонмону тандоо"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"Мобилдик трафик"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"Туташты"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"Мобилдик трафик автоматтык түрдө туташтырылбайт"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"Байланыш жок"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Башка тармактар жеткиликсиз"</string>
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index 4fc25e2..d7dbb86 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ຄວາມແຈ້ງ"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"ການປີ້ນສີ"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"ການແກ້ໄຂສີ"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"ຕັ້ງຄ່າຜູ້ໃຊ້"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"ແລ້ວໆ"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"ປິດ"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"ເຊື່ອມ​ຕໍ່ແລ້ວ"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"ປິດອິນເຕີເນັດມືຖືໄວ້ບໍ?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"ທ່ານຈະບໍ່ມີສິດເຂົ້າເຖິງຂໍ້ມູນ ຫຼື ອິນເຕີເນັດຜ່ານ <xliff:g id="CARRIER">%s</xliff:g>. ອິນເຕີເນັດຈະສາມາດໃຊ້ໄດ້ຜ່ານ Wi-Fi ເທົ່ານັ້ນ."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"ຜູ້​ໃຫ້​ບໍ​ລິ​ການ​ຂອງ​ທ່ານ"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"ເນື່ອງຈາກມີແອັບໃດໜຶ່ງກຳລັງຂັດຂວາງການຂໍອະນຸຍາດ, ການຕັ້ງຄ່າຈຶ່ງບໍ່ສາມາດຢັ້ງຢືນການຕອບຮັບຂອງທ່ານໄດ້."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"ອະນຸຍາດ <xliff:g id="APP_0">%1$s</xliff:g> ໃຫ້ສະແດງ <xliff:g id="APP_2">%2$s</xliff:g> ສະໄລ້ບໍ?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- ມັນສາມາດອ່ານຂໍ້ມູນຈາກ <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"ການຕັ້ງຄ່າໜ້າຈໍຂະຫຍາຍ"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"ແຕະເພື່ອເປີດຄຸນສົມບັດການຊ່ວຍເຂົ້າເຖິງ. ປັບແຕ່ງ ຫຼື ປ່ຽນປຸ່ມນີ້ໃນການຕັ້ງຄ່າ.\n\n"<annotation id="link">"ເບິ່ງການຕັ້ງຄ່າ"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"ຍ້າຍປຸ່ມໄປໃສ່ຂອບເພື່ອເຊື່ອງມັນຊົ່ວຄາວ"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ຍ້າຍຊ້າຍເທິງ"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"ຍ້າຍຂວາເທິງ"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"ຍ້າຍຊ້າຍລຸ່ມ"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"ຍ້າຍຂວາລຸ່ມ"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"ຍ້າຍອອກຂອບ ແລະ ເຊື່ອງ"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"ຍ້າຍອອກຂອບ ແລະ ສະແດງ"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"ສະຫຼັບ"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"ການຄວບຄຸມອຸປະກອນ"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"ເລືອກແອັບເພື່ອເພີ່ມການຄວບຄຸມ"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"ອິນເຕີເນັດມືຖື"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"ເຊື່ອມຕໍ່ແລ້ວ"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"ຈະບໍ່ເຊື່ອມຕໍ່ອິນເຕີເນັດມືຖືອັດຕະໂນມັດ"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"ບໍ່ມີການເຊື່ອມຕໍ່"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"ບໍ່ມີເຄືອຂ່າຍອື່ນທີ່ສາມາດໃຊ້ໄດ້"</string>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index b9d6f33..e631ce2 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Šviesumas"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Spalvų inversija"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Spalvų taisymas"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Naudotojo nustatymai"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"Atlikta"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Uždaryti"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"Prijungtas"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"Išjungti mobiliojo ryšio duomenis?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"Naudodamiesi „<xliff:g id="CARRIER">%s</xliff:g>“ paslaugomis neturėsite galimybės pasiekti duomenų arba interneto. Internetą galėsite naudoti tik prisijungę prie „Wi-Fi“."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"savo operatoriaus"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"Kadangi programa užstoja leidimo užklausą, nustatymuose negalima patvirtinti jūsų atsakymo."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"Leisti „<xliff:g id="APP_0">%1$s</xliff:g>“ rodyti „<xliff:g id="APP_2">%2$s</xliff:g>“ fragmentus?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- Gali nuskaityti informaciją iš „<xliff:g id="APP">%1$s</xliff:g>“"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Didinimo lango nustatymai"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Palietę atidarykite pritaikymo neįgaliesiems funkcijas. Tinkinkite arba pakeiskite šį mygtuką nustatymuose.\n\n"<annotation id="link">"Žr. nustatymus"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Perkelkite mygtuką prie krašto, kad laikinai jį paslėptumėte"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Perkelti į viršų kairėje"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Perkelti į viršų dešinėje"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Perkelti į apačią kairėje"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Perkelti į apačią dešinėje"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Perkelti į kraštą ir slėpti"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Perkelti iš krašto ir rodyti"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"perjungti"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Įrenginio valdikliai"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"Pasirinkite programą, kad pridėtumėte valdiklių"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobiliojo ryšio duomenys"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"Prisijungta"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"Naud. mob. r. duomenis nebus autom. prisijungiama"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"Nėra ryšio"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Nėra kitų pasiekiamų tinklų"</string>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index 882ff7c..0a08654 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Spilgtums"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Krāsu inversija"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Krāsu korekcija"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Lietotāja iestatījumi"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"Gatavs"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Aizvērt"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"Pievienota"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"Vai izslēgt mobilos datus?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"Izmantojot mobilo sakaru operatora <xliff:g id="CARRIER">%s</xliff:g> pakalpojumus, nevarēsiet piekļūt datiem vai internetam. Internetam varēsiet piekļūt, tikai izmantojot Wi-Fi savienojumu."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"jūsu mobilo sakaru operators"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"Lietotne Iestatījumi nevar verificēt jūsu atbildi, jo cita lietotne aizsedz atļaujas pieprasījumu."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"Vai atļaut lietotnei <xliff:g id="APP_0">%1$s</xliff:g> rādīt lietotnes <xliff:g id="APP_2">%2$s</xliff:g> sadaļas?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- Var lasīt informāciju no lietotnes <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Lupas loga iestatījumi"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Atveriet pieejamības funkcijas. Pielāgojiet vai aizstājiet šo pogu iestatījumos.\n\n"<annotation id="link">"Skatīt iestatījumus"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Lai īslaicīgi paslēptu pogu, pārvietojiet to uz malu"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Pārvietot augšpusē pa kreisi"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Pārvietot augšpusē pa labi"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Pārvietot apakšpusē pa kreisi"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Pārvietot apakšpusē pa labi"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Pārvietot uz malu un paslēpt"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Pārvietot no malas un parādīt"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"pārslēgt"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Ierīču vadīklas"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"Izvēlieties lietotni, lai pievienotu vadīklas"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobilie dati"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"Ir izveidots savienojums"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobilo datu savienojums netiks veidots automātiski"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"Nav savienojuma"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Nav pieejams neviens cits tīkls"</string>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index 0d98dc6..e714055 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Осветленост"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Инверзија на боите"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Корекција на боите"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Кориснички поставки"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"Готово"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Затвори"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"Поврзано"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"Да се исклучи мобилниот интернет?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"Нема да имате пристап до податоците или интернетот преку <xliff:g id="CARRIER">%s</xliff:g>. Интернетот ќе биде достапен само преку Wi-Fi."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"вашиот оператор"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"Бидејќи апликацијата го прикрива барањето за дозвола, „Поставките“ не може да го потврдат вашиот одговор."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"Да се дозволи <xliff:g id="APP_0">%1$s</xliff:g> да прикажува делови од <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- Може да чита информации од <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Поставки за прозорец за лупа"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Допрете за функциите за пристапност. Приспособете или заменете го копчево во „Поставки“.\n\n"<annotation id="link">"Прикажи поставки"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Преместете го копчето до работ за да го сокриете привремено"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Премести горе лево"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Премести горе десно"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Премести долу лево"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Премести долу десно"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Премести до работ и сокриј"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Премести над работ и прикажи"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"вклучување/исклучување"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Контроли за уредите"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"Изберете апликација за да додадете контроли"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"Мобилен интернет"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"Поврзано"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"Мобилниот интернет не може да се поврзе автоматски"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"Нема интернет-врска"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Нема други достапни мрежи"</string>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index 868d759..666e601 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -248,7 +248,7 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"തെളിച്ചം"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"നിറം വിപരീതമാക്കൽ"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"നിറം ശരിയാക്കൽ"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"ഉപയോക്തൃ ക്രമീകരണം"</string>
+    <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"ഉപയോക്താക്കളെ മാനേജ് ചെയ്യുക"</string>
     <string name="quick_settings_done" msgid="2163641301648855793">"പൂർത്തിയാക്കി"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"അടയ്ക്കുക"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"കണക്‌റ്റുചെയ്‌തു"</string>
@@ -727,6 +727,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"മൊബൈൽ ഡാറ്റ ഓഫാക്കണോ?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"നിങ്ങൾക്ക് ഡാറ്റയിലേക്ക് ആക്‌സസോ അല്ലെങ്കിൽ <xliff:g id="CARRIER">%s</xliff:g> മുഖേനയുള്ള ഇന്റർനെറ്റോ ഉണ്ടാകില്ല. വൈഫൈ മുഖേന മാത്രമായിരിക്കും ഇന്റർനെറ്റ് ലഭ്യത."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"നിങ്ങളുടെ കാരിയർ"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"അനുമതി അഭ്യർത്ഥനയെ ഒരു ആപ്പ് മറയ്‌ക്കുന്നതിനാൽ, ക്രമീകരണത്തിന് നിങ്ങളുടെ പ്രതികരണം പരിശോധിച്ചുറപ്പിക്കാനാകില്ല."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_2">%2$s</xliff:g> സ്ലൈസുകൾ കാണിക്കാൻ <xliff:g id="APP_0">%1$s</xliff:g>-നെ അനുവദിക്കണോ?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- ഇതിന് <xliff:g id="APP">%1$s</xliff:g>-ൽ നിന്ന് വിവരങ്ങൾ വായിക്കാനാകും"</string>
@@ -785,12 +793,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"മാഗ്നിഫയർ വിൻഡോ ക്രമീകരണം"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"ഉപയോഗസഹായി ഫീച്ചർ തുറക്കാൻ ടാപ്പ് ചെയ്യൂ. ക്രമീകരണത്തിൽ ഈ ബട്ടൺ ഇഷ്ടാനുസൃതമാക്കാം, മാറ്റാം.\n\n"<annotation id="link">"ക്രമീകരണം കാണൂ"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"തൽക്കാലം മറയ്‌ക്കുന്നതിന് ബട്ടൺ അരുകിലേക്ക് നീക്കുക"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"മുകളിൽ ഇടതുഭാഗത്തേക്ക് നീക്കുക"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"മുകളിൽ വലതുഭാഗത്തേക്ക് നീക്കുക"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"ചുവടെ ഇടതുഭാഗത്തേക്ക് നീക്കുക"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"ചുവടെ വലതുഭാഗത്തേക്ക് നീക്കുക"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"എഡ്‌ജിലേക്ക് നീക്കി മറയ്‌ക്കുക"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"എഡ്‌ജിൽ നിന്ന് നീക്കി കാണിക്കൂ"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"മാറ്റുക"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"ഉപകരണ നിയന്ത്രണങ്ങൾ"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"നിയന്ത്രണങ്ങൾ ചേർക്കാൻ ആപ്പ് തിരഞ്ഞെടുക്കുക"</string>
@@ -933,6 +947,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"മൊബൈൽ ഡാറ്റ"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"കണക്റ്റ് ചെയ്തു"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"മൊബൈൽ ഡാറ്റ സ്വയം കണക്റ്റ് ചെയ്യില്ല"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"കണക്ഷനില്ല"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"മറ്റ് നെറ്റ്‌വർക്കുകളൊന്നും ലഭ്യമല്ല"</string>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index 25929e6..769cdda 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -248,7 +248,7 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Тодрол"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Өнгө хувиргалт"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Өнгө тохируулга"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Хэрэглэгчийн тохиргоо"</string>
+    <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Хэрэглэгчдийг удирдах"</string>
     <string name="quick_settings_done" msgid="2163641301648855793">"Дууссан"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Хаах"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"Холбогдсон"</string>
@@ -727,6 +727,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"Мобайл датаг унтраах уу?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"Та <xliff:g id="CARRIER">%s</xliff:g>-р дата эсвэл интернэтэд хандах боломжгүй болно. Интернэтэд зөвхөн Wi-Fi-р холбогдох боломжтой болно."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"таны оператор компани"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"Апп нь зөвшөөрлийн хүсэлтийг танихгүй байгаа тул Тохиргооноос таны хариултыг баталгаажуулах боломжгүй байна."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g>-д <xliff:g id="APP_2">%2$s</xliff:g>-н хэсгүүдийг (slices) харуулахыг зөвшөөрөх үү?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- Энэ нь <xliff:g id="APP">%1$s</xliff:g>-с мэдээлэл унших боломжтой"</string>
@@ -785,12 +793,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Томруулагчийн цонхны тохиргоо"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Хандалтын онцлогуудыг нээхийн тулд товшино уу. Энэ товчлуурыг Тохиргоо хэсэгт өөрчилж эсвэл солиорой.\n\n"<annotation id="link">"Тохиргоог харах"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Үүнийг түр нуухын тулд товчлуурыг зах руу зөөнө үү"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Зүүн дээш зөөх"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Баруун дээш зөөх"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Зүүн доош зөөх"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Баруун доош зөөх"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Ирмэг рүү зөөж, нуух"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Ирмэгээс гаргаж, харуулах"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"асаах/унтраах"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Төхөөрөмжийн хяналт"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"Хяналтууд нэмэхийн тулд аппыг сонгоно уу"</string>
@@ -933,6 +947,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"Мобайл дата"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"Холбогдсон"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"Мобайл дата автоматаар холбогдохгүй"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"Холболт алга"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Өөр боломжтой сүлжээ байхгүй байна"</string>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index c6e99a7..d45b1e5 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"चमक"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"कलर इन्व्हर्जन"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"रंग सुधारणा"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"वापरकर्ता सेटिंग्ज"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"पूर्ण झाले"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"बंद करा"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"कनेक्ट केलेले"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"मोबाइल डेटा बंद करायचा?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"तुम्हाला <xliff:g id="CARRIER">%s</xliff:g> मधून डेटा किंवा इंटरनेटचा अ‍ॅक्सेस नसेल. इंटरनेट फक्त वाय-फाय मार्फत उपलब्ध असेल."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"तुमचा वाहक"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"अ‍ॅप परवानगी विनंती अस्पष्‍ट करत असल्‍याने, सेटिंग्ज तुमचा प्रतिसाद पडताळू शकत नाहीत."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> ला <xliff:g id="APP_2">%2$s</xliff:g> चे तुकडे दाखवण्याची अनुमती द्यायची का?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- ते <xliff:g id="APP">%1$s</xliff:g> ची माहिती वाचू शकते"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"मॅग्निफायर विंडो सेटिंग्ज"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"अ‍ॅक्सेसिबिलिटी वैशिष्ट्ये उघडण्यासाठी, टॅप करा. सेटिंग्जमध्ये हे बटण कस्टमाइझ करा किंवा बदला.\n\n"<annotation id="link">"सेटिंग्ज पहा"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"बटण तात्पुरते लपवण्यासाठी ते कोपर्‍यामध्ये हलवा"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"वर डावीकडे हलवा"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"वर उजवीकडे हलवा"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"तळाशी डावीकडे हलवा"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"तळाशी उजवीकडे हलवा"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"एजवर हलवा आणि लपवा"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"एजवर हलवा आणि दाखवा"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"टॉगल करा"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"डिव्हाइस नियंत्रणे"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"नियंत्रणे जोडण्यासाठी ॲप निवडा"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"मोबाइल डेटा"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"कनेक्ट केले आहे"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"मोबाइल डेटा ऑटो-कनेक्ट होणार नाही"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"कोणतेही कनेक्शन नाही"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"इतर कोणतेही नेटवर्क उपलब्ध नाहीत"</string>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index 99349b157..9e43396 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Kecerahan"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Penyongsangan warna"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Pembetulan warna"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Tetapan pengguna"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"Selesai"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Tutup"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"Disambungkan"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"Matikan data mudah alih?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"Anda tidak akan mempunyai akses kepada data atau Internet melalui <xliff:g id="CARRIER">%s</xliff:g>. Internet hanya tersedia melaui Wi-Fi."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"pembawa anda"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"Oleh sebab apl melindungi permintaan kebenaran, Tetapan tidak dapat mengesahkan jawapan anda."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"Benarkan <xliff:g id="APP_0">%1$s</xliff:g> menunjukkan <xliff:g id="APP_2">%2$s</xliff:g> hirisan?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- Hos hirisan boleh membaca maklumat daripada <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Tetapan tetingkap penggadang"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Ketik untuk membuka ciri kebolehaksesan. Sesuaikan/gantikan butang ini dalam Tetapan.\n\n"<annotation id="link">"Lihat tetapan"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Gerakkan butang ke tepi untuk disembunyikan buat sementara waktu"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Alihkan ke atas sebelah kiri"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Alihkan ke atas sebelah kanan"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Alihkan ke bawah sebelah kiri"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Alihkan ke bawah sebelah kanan"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Alihkan ke tepi dan sorokkan"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Alihkan ke tepi dan tunjukkan"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"togol"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Kawalan peranti"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"Pilih apl untuk menambahkan kawalan"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"Data mudah alih"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"Disambungkan"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"Data mudah alih tidak akan autosambung"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"Tiada sambungan"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Tiada rangkaian lain yang tersedia"</string>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index f30846a..8f404b8 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"အလင်းတောက်ပမှု"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"အရောင်ပြောင်းပြန်ပြုလုပ်ရန်"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"အရောင် အမှန်ပြင်ခြင်း"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"အသုံးပြုသူ ဆက်တင်များ"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"ပြီးပါပြီ"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"ပိတ်ရန်"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"ချိတ်ဆက်ထား"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"မိုဘိုင်းဒေတာ ပိတ်မလား။"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"<xliff:g id="CARRIER">%s</xliff:g> မှတစ်ဆင့် ဒေတာ သို့မဟုတ် အင်တာနက်ကို မသုံးနိုင်ပါ။ Wi-Fi ဖြင့်သာ အင်တာနက် သုံးနိုင်သည်။"</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"သင်၏ ဝန်ဆောင်မှုပေးသူ"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"အပလီကေးရှင်းတစ်ခုက ခွင့်ပြုချက်တောင်းခံမှုကို ပိတ်ထားသောကြောင့် ဆက်တင်များသည် သင်၏ လုပ်ဆောင်ကို တုံ့ပြန်နိုင်ခြင်းမရှိပါ။"</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> အား <xliff:g id="APP_2">%2$s</xliff:g> ၏အချပ်များ ပြသခွင့်ပြုပါသလား။"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- ၎င်းသည် <xliff:g id="APP">%1$s</xliff:g> မှ အချက်အလက်ကို ဖတ်နိုင်သည်"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"မှန်ဘီလူးဝင်းဒိုး ဆက်တင်များ"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"အများသုံးစွဲနိုင်မှုဆိုင်ရာ ဝန်ဆောင်မှုများ ဖွင့်ရန် တို့ပါ။ ဆက်တင်များတွင် ဤခလုတ်ကို စိတ်ကြိုက်ပြင်ပါ (သို့) လဲပါ။\n\n"<annotation id="link">"ဆက်တင်များ ကြည့်ရန်"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"ခလုတ်ကို ယာယီဝှက်ရန် အစွန်းသို့ရွှေ့ပါ"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ဘယ်ဘက်ထိပ်သို့ ရွှေ့ရန်"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"ညာဘက်ထိပ်သို့ ရွှေ့ရန်"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"ဘယ်ဘက်အောက်ခြေသို့ ရွှေ့ရန်"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"ညာဘက်အောက်ခြေသို့ ရွှေ့ရန်"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"အစွန်းသို့ရွှေ့ပြီး ဝှက်ရန်"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"အစွန်းမှရွှေ့ပြီး ပြရန်"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"ပြောင်းရန်"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"စက်ထိန်းစနစ်"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"ထိန်းချုပ်မှုများထည့်ရန် အက်ပ်ရွေးခြင်း"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"မိုဘိုင်းဒေတာ"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"ချိတ်ဆက်ထားသည်"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"မိုဘိုင်းဒေတာ အော်တိုမချိတ်ပါ"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"ချိတ်ဆက်မှုမရှိပါ"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"အခြားကွန်ရက်များ မရှိပါ"</string>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index 117c864..94b8e90 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Lysstyrke"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Fargeinvertering"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Fargekorrigering"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Brukerinnstillinger"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"Ferdig"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Lukk"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"Tilkoblet"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"Vil du slå av mobildata?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"Du får ikke tilgang til data eller internett via <xliff:g id="CARRIER">%s</xliff:g>. Internett er bare tilgjengelig via Wifi."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"operatøren din"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"Fordi en app skjuler tillatelsesforespørselen, kan ikke Innstillinger bekrefte svaret ditt."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"Vil du tillate at <xliff:g id="APP_0">%1$s</xliff:g> viser <xliff:g id="APP_2">%2$s</xliff:g>-utsnitt?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"– Den kan lese informasjon fra <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Innstillinger for forstørringsvindu"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Trykk for å åpne tilgj.funksjoner. Tilpass eller bytt knappen i Innstillinger.\n\n"<annotation id="link">"Se innstillingene"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Flytt knappen til kanten for å skjule den midlertidig"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Flytt til øverst til venstre"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Flytt til øverst til høyre"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Flytt til nederst til venstre"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Flytt til nederst til høyre"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Flytt til kanten og skjul"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Flytt ut kanten og vis"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"slå av/på"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Enhetsstyring"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"Velg en app for å legge til kontroller"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobildata"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"Tilkoblet"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobildata kobler ikke til automatisk"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"Ingen tilkobling"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Ingen andre nettverk er tilgjengelige"</string>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index 3519715..35937a9 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"उज्यालपन"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"कलर इन्भर्सन"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"कलर करेक्सन"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"प्रयोगकर्तासम्बन्धी सेटिङ"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"भयो"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"बन्द गर्नुहोस्"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"जोडिएको"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"मोबाइल डेटा निष्क्रिय पार्ने हो?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"तपाईं <xliff:g id="CARRIER">%s</xliff:g> मार्फत डेटा वा इन्टरनेट प्रयोग गर्न सक्नुहुने छैन। Wi-Fi मार्फत मात्र इन्टरनेट उपलब्ध हुने छ।"</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"तपाईंको सेवा प्रदायक"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"कुनै एपको कारणले अनुमतिसम्बन्धी अनुरोध बुझ्न गाह्रो भइरहेकोले सेटिङहरूले तपाईंको प्रतिक्रिया प्रमाणित गर्न सक्दैनन्।"</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> लाई <xliff:g id="APP_2">%2$s</xliff:g> का स्लाइसहरू देखाउन अनुमति दिने हो?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- यसले <xliff:g id="APP">%1$s</xliff:g> बाट जानकारी पढ्न सक्छ"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"म्याग्निफायर विन्डोसम्बन्धी सेटिङ"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"सर्वसुलभता कायम गर्ने सुविधा खोल्न ट्याप गर्नुहोस्। सेटिङमा गई यो बटन कस्टमाइज गर्नुहोस् वा बदल्नुहोस्।\n\n"<annotation id="link">"सेटिङ हेर्नुहोस्"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"यो बटन केही बेर नदेखिने पार्न किनारातिर सार्नुहोस्"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"सिरानको बायाँतिर सार्नुहोस्"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"सिरानको दायाँतिर सार्नुहोस्"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"पुछारको बायाँतिर सार्नुहोस्"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"पुछारको दायाँतिर सार्नुहोस्"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"किनारामा सार्नुहोस् र नदेखिने पार्नु…"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"किनाराबाट सार्नुहोस् र देखिने पार्नु…"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"टगल गर्नुहोस्"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"डिभाइस नियन्त्रण गर्ने विजेटहरू"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"कन्ट्रोल थप्नु पर्ने एप छान्नुहोस्"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"मोबाइल डेटा"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"इन्टरनेटमा कनेक्ट गरिएको छ"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"मोबाइल डेटा स्वतः कनेक्ट हुँदैन"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"इन्टरनेट छैन"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"अन्य नेटवर्क उपलब्ध छैनन्"</string>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index 3c85148..4de0331 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Helderheid"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Kleurinversie"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Kleurcorrectie"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Gebruikersinstellingen"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"Klaar"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Sluiten"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"Verbonden"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"Mobiele data uitzetten?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"Je hebt dan geen toegang meer tot data of internet via <xliff:g id="CARRIER">%s</xliff:g>. Internet is alleen nog beschikbaar via wifi."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"je provider"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"Aangezien een app een rechtenverzoek afdekt, kan Instellingen je reactie niet verifiëren."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> toestaan om segmenten van <xliff:g id="APP_2">%2$s</xliff:g> te tonen?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- Deze kan informatie lezen van <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Instellingen voor vergrotingsvenster"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Tik voor toegankelijkheidsfuncties. Wijzig of vervang deze knop via Instellingen.\n\n"<annotation id="link">"Naar Instellingen"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Knop naar de rand verplaatsen om deze tijdelijk te verbergen"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Naar linksboven verplaatsen"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Naar rechtsboven verplaatsen"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Naar linksonder verplaatsen"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Naar rechtsonder verplaatsen"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Naar rand verplaatsen en verbergen"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Over rand verplaatsen en tonen"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"schakelen"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Apparaatbediening"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"Kies de app waaraan je bedieningselementen wilt toevoegen"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobiele data"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"Verbonden"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobiele data maakt niet automatisch verbinding"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"Geen verbinding"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Geen andere netwerken beschikbaar"</string>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index 1cb3436..16941b8 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ଉଜ୍ଜ୍ୱଳତା"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"ରଙ୍ଗ ଇନଭାର୍ସନ"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"ରଙ୍ଗ ସଂଶୋଧନ"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"ଉପଯୋଗକର୍ତ୍ତା ସେଟିଂସ"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"ହୋଇଗଲା"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"ବନ୍ଦ କରନ୍ତୁ"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"ସଂଯୁକ୍ତ"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"ମୋବାଇଲ୍‌ ଡାଟା ବନ୍ଦ କରିବେ?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"ଡାଟା କିମ୍ବା ଇଣ୍ଟରନେଟ୍‌କୁ <xliff:g id="CARRIER">%s</xliff:g> ଦ୍ଵାରା ଆପଣଙ୍କର  ଆକ୍ସେସ୍ ରହିବ ନାହିଁ। ଇଣ୍ଟରନେଟ୍‌ କେବଳ ୱାଇ-ଫାଇ ମାଧ୍ୟମରେ ଉପଲବ୍ଧ ହେବ।"</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"ଆପଣଙ୍କ କେରିଅର୍"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"ଗୋଟିଏ ଆପ୍‍ ଏକ ଅନୁମତି ଅନୁରୋଧକୁ ଦେଖିବାରେ ବାଧା ଦେଉଥିବାରୁ, ସେଟିଙ୍ଗ ଆପଣଙ୍କ ଉତ୍ତରକୁ ଯାଞ୍ଚ କରିପାରିବ ନାହିଁ।"</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_2">%2$s</xliff:g> ସ୍ଲାଇସ୍‌କୁ ଦେଖାଇବା ପାଇଁ <xliff:g id="APP_0">%1$s</xliff:g>କୁ ଅନୁମତି ଦେବେ?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- ଏହା <xliff:g id="APP">%1$s</xliff:g>ରୁ ସୂଚନାକୁ ପଢ଼ିପାରିବ"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"ମ୍ୟାଗ୍ନିଫାୟର ୱିଣ୍ଡୋର ସେଟିଂସ"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"ଆକ୍ସେସିବିଲିଟୀ ଫିଚର ଖୋଲିବାକୁ ଟାପ କରନ୍ତୁ। ସେଟିଂସରେ ଏହି ବଟନକୁ କଷ୍ଟମାଇଜ କର କିମ୍ବା ବଦଳାଅ।\n\n"<annotation id="link">"ସେଟିଂସ ଦେଖନ୍ତୁ"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"ବଟନକୁ ଅସ୍ଥାୟୀ ଭାବେ ଲୁଚାଇବା ପାଇଁ ଏହାକୁ ଗୋଟିଏ ଧାରକୁ ମୁଭ୍ କରନ୍ତୁ"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ଶୀର୍ଷ ବାମକୁ ମୁଭ୍ କରନ୍ତୁ"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"ଶୀର୍ଷ ଡାହାଣକୁ ମୁଭ୍ କରନ୍ତୁ"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"ନିମ୍ନ ବାମକୁ ମୁଭ୍ କରନ୍ତୁ"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"ନିମ୍ନ ଡାହାଣକୁ ମୁଭ୍ କରନ୍ତୁ"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"ଧାରକୁ ମୁଭ୍ କରି ଲୁଚାନ୍ତୁ"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"ଧାର ବାହାରକୁ ମୁଭ୍ କରି ଦେଖାନ୍ତୁ"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"ଟୋଗଲ୍ କରନ୍ତୁ"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"ଡିଭାଇସ୍ ନିୟନ୍ତ୍ରଣଗୁଡ଼ିକ"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"ନିୟନ୍ତ୍ରଣଗୁଡ଼ିକୁ ଯୋଗ କରିବାକୁ ଆପ୍ ବାଛନ୍ତୁ"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"ମୋବାଇଲ ଡାଟା"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"ସଂଯୋଗ କରାଯାଇଛି"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"ମୋବାଇଲ ଡାଟା ସ୍ୱତଃ-ସଂଯୋଗ ହେବ ନାହିଁ"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"ସଂଯୋଗ ନାହିଁ"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"ଅନ୍ୟ କୌଣସି ନେଟୱାର୍କ ଉପଲବ୍ଧ ନାହିଁ"</string>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index 992ffd6..fce69ec 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ਚਮਕ"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"ਰੰਗ ਪਲਟਨਾ"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"ਰੰਗ ਸੁਧਾਈ"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"ਵਰਤੋਂਕਾਰ ਸੈਟਿੰਗਾਂ"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"ਹੋ ਗਿਆ"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"ਬੰਦ ਕਰੋ"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"ਕਨੈਕਟ ਕੀਤਾ"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"ਕੀ ਮੋਬਾਈਲ ਡਾਟਾ ਬੰਦ ਕਰਨਾ ਹੈ?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"ਤੁਸੀਂ <xliff:g id="CARRIER">%s</xliff:g> ਰਾਹੀਂ ਡਾਟੇ ਜਾਂ ਇੰਟਰਨੈੱਟ ਤੱਕ ਪਹੁੰਚ ਨਹੀਂ ਕਰ ਸਕੋਗੇ। ਇੰਟਰਨੈੱਟ ਸਿਰਫ਼ ਵਾਈ-ਫਾਈ ਰਾਹੀਂ ਉਪਲਬਧ ਹੋਵੇਗਾ।"</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"ਤੁਹਾਡਾ ਕੈਰੀਅਰ"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"ਕਿਸੇ ਐਪ ਵੱਲੋਂ ਇਜਾਜ਼ਤ ਬੇਨਤੀ ਨੂੰ ਢਕੇ ਜਾਣ ਕਾਰਨ ਸੈਟਿੰਗਾਂ ਤੁਹਾਡੇ ਜਵਾਬ ਦੀ ਪੁਸ਼ਟੀ ਨਹੀਂ ਕਰ ਸਕਦੀਆਂ।"</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"ਕੀ <xliff:g id="APP_0">%1$s</xliff:g> ਨੂੰ <xliff:g id="APP_2">%2$s</xliff:g> ਦੇ ਹਿੱਸੇ ਦਿਖਾਉਣ ਦੇਣੇ ਹਨ?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- ਇਹ <xliff:g id="APP">%1$s</xliff:g> ਵਿੱਚੋਂ ਜਾਣਕਾਰੀ ਪੜ੍ਹ ਸਕਦਾ ਹੈ"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"ਵੱਡਦਰਸ਼ੀ ਵਿੰਡੋ ਸੈਟਿੰਗਾਂ"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"ਪਹੁੰਚਯੋਗਤਾ ਵਿਸ਼ੇਸ਼ਤਾਵਾਂ ਖੋਲ੍ਹਣ ਲਈ ਟੈਪ ਕਰੋ। ਸੈਟਿੰਗਾਂ ਵਿੱਚ ਇਹ ਬਟਨ ਵਿਉਂਤਬੱਧ ਕਰੋ ਜਾਂ ਬਦਲੋ।\n\n"<annotation id="link">"ਸੈਟਿੰਗਾਂ ਦੇਖੋ"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"ਬਟਨ ਨੂੰ ਅਸਥਾਈ ਤੌਰ \'ਤੇ ਲੁਕਾਉਣ ਲਈ ਕਿਨਾਰੇ \'ਤੇ ਲਿਜਾਓ"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ਉੱਪਰ ਵੱਲ ਖੱਬੇ ਲਿਜਾਓ"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"ਉੱਪਰ ਵੱਲ ਸੱਜੇ ਲਿਜਾਓ"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"ਹੇਠਾਂ ਵੱਲ ਖੱਬੇ ਲਿਜਾਓ"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"ਹੇਠਾਂ ਵੱਲ ਸੱਜੇ ਲਿਜਾਓ"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"ਕਿਨਾਰੇ ਵਿੱਚ ਲਿਜਾ ਕੇ ਲੁਕਾਓ"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"ਕਿਨਾਰੇ ਤੋਂ ਬਾਹਰ ਕੱਢ ਕੇ ਦਿਖਾਓ"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"ਟੌਗਲ ਕਰੋ"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"ਡੀਵਾਈਸ ਕੰਟਰੋਲ"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"ਕੰਟਰੋਲ ਸ਼ਾਮਲ ਕਰਨ ਲਈ ਐਪ ਚੁਣੋ"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"ਮੋਬਾਈਲ ਡਾਟਾ"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"ਕਨੈਕਟ ਹੈ"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"ਮੋਬਾਈਲ ਡਾਟਾ ਸਵੈ-ਕਨੈਕਟ ਨਹੀਂ ਹੋਵੇਗਾ"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"ਕੋਈ ਕਨੈਕਸ਼ਨ ਨਹੀਂ"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"ਕੋਈ ਹੋਰ ਨੈੱਟਵਰਕ ਉਪਲਬਧ ਨਹੀਂ ਹੈ"</string>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index b706359..8787f36 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Jasność"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Odwrócenie kolorów"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Korekcja kolorów"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Ustawienia użytkownika"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"Gotowe"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Zamknij"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"Połączono"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"Wyłączyć mobilną transmisję danych?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"Nie będziesz mieć dostępu do transmisji danych ani internetu w <xliff:g id="CARRIER">%s</xliff:g>. Internet będzie dostępny tylko przez Wi‑Fi."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"Twój operator"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"Aplikacja Ustawienia nie może zweryfikować Twojej odpowiedzi, ponieważ inna aplikacja zasłania prośbę o udzielenie uprawnień."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"Zezwolić aplikacji <xliff:g id="APP_0">%1$s</xliff:g> na pokazywanie wycinków z aplikacji <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- Może odczytywać informacje z aplikacji <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Ustawienia okna powiększania"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Kliknij, aby otworzyć ułatwienia dostępu. Dostosuj lub zmień ten przycisk w Ustawieniach.\n\n"<annotation id="link">"Wyświetl ustawienia"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Przesuń przycisk do krawędzi, aby ukryć go tymczasowo"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Przenieś w lewy górny róg"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Przenieś w prawy górny róg"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Przenieś w lewy dolny róg"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Przenieś w prawy dolny róg"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Przenieś do krawędzi i ukryj"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Przenieś poza krawędź i pokaż"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"przełącz"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Sterowanie urządzeniami"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"Wybierz aplikację, do której chcesz dodać elementy sterujące"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobilna transmisja danych"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"Połączono"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobilna transmisja danych nie połączy się automatycznie"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"Brak połączenia"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Brak innych dostępnych sieci"</string>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index 78a6409..e6f0580 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brilho"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversão de cores"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Correção de cor"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Config. do usuário"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"Concluído"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Fechar"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"Conectado"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"Desativar os dados móveis?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"Você não terá acesso a dados ou à Internet pela operadora <xliff:g id="CARRIER">%s</xliff:g>. A Internet só estará disponível via Wi-Fi."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"sua operadora"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"Como um app está ocultando uma solicitação de permissão, as configurações não podem verificar sua resposta."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"Permitir que <xliff:g id="APP_0">%1$s</xliff:g> mostre partes do app <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- Pode ler informações do app <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Configurações da janela de lupa"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Toque para abrir os recursos de acessibilidade. Personalize ou substitua o botão nas Configurações.\n\n"<annotation id="link">"Ver configurações"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Mova o botão para a borda para ocultá-lo temporariamente"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Mover para o canto superior esquerdo"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Mover para o canto superior direito"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Mover para o canto inferior esquerdo"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Mover para o canto inferior direito"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Mover para a borda e ocultar"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Mover para fora da borda e exibir"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"alternar"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Controles do dispositivo"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"Escolha um app para adicionar controles"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"Dados móveis"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"Conectado"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"Sem conexão automática com dados móveis"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"Sem conexão"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Nenhuma outra rede disponível"</string>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index 30233b3..6c07ce0 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -248,7 +248,7 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brilho"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversão de cores"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Correção da cor"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Definições do utilizador"</string>
+    <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Gerir utilizadores"</string>
     <string name="quick_settings_done" msgid="2163641301648855793">"Concluído"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Fechar"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"Ligado"</string>
@@ -727,6 +727,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"Desativar os dados móveis?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"Não terá acesso a dados ou à Internet através do operador <xliff:g id="CARRIER">%s</xliff:g>. A Internet estará disponível apenas por Wi-Fi."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"o seu operador"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"Uma vez que uma app está a ocultar um pedido de autorização, as Definições não conseguem validar a sua resposta."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"Permitir que a app <xliff:g id="APP_0">%1$s</xliff:g> mostre partes da app <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- Pode ler informações da app <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -785,12 +793,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Definições da janela da lupa"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Toque para abrir funcionalidades de acessibilidade. Personal. ou substitua botão em Defin.\n\n"<annotation id="link">"Ver defin."</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Mova o botão para a extremidade para o ocultar temporariamente"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Mover p/ parte sup. esquerda"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Mover parte superior direita"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Mover p/ parte infer. esquerda"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Mover parte inferior direita"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Mover p/ extremidade e ocultar"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Retirar extremidade e mostrar"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"ativar/desativar"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Controlos de dispositivos"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"Escolha uma app para adicionar controlos"</string>
@@ -933,6 +947,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"Dados móveis"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"Ligado"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"Sem ligação automática com dados móveis"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"Sem ligação"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Nenhuma outra rede disponível"</string>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index 78a6409..e6f0580 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brilho"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversão de cores"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Correção de cor"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Config. do usuário"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"Concluído"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Fechar"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"Conectado"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"Desativar os dados móveis?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"Você não terá acesso a dados ou à Internet pela operadora <xliff:g id="CARRIER">%s</xliff:g>. A Internet só estará disponível via Wi-Fi."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"sua operadora"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"Como um app está ocultando uma solicitação de permissão, as configurações não podem verificar sua resposta."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"Permitir que <xliff:g id="APP_0">%1$s</xliff:g> mostre partes do app <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- Pode ler informações do app <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Configurações da janela de lupa"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Toque para abrir os recursos de acessibilidade. Personalize ou substitua o botão nas Configurações.\n\n"<annotation id="link">"Ver configurações"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Mova o botão para a borda para ocultá-lo temporariamente"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Mover para o canto superior esquerdo"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Mover para o canto superior direito"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Mover para o canto inferior esquerdo"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Mover para o canto inferior direito"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Mover para a borda e ocultar"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Mover para fora da borda e exibir"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"alternar"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Controles do dispositivo"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"Escolha um app para adicionar controles"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"Dados móveis"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"Conectado"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"Sem conexão automática com dados móveis"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"Sem conexão"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Nenhuma outra rede disponível"</string>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index 0d96062..080edfd 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Luminozitate"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inversarea culorilor"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Corecția culorii"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Setări de utilizator"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"Terminat"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Închide"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"Conectat"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"Dezactivezi datele mobile?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"Nu vei avea acces la date sau la internet prin intermediul <xliff:g id="CARRIER">%s</xliff:g>. Internetul va fi disponibil numai prin Wi-Fi."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"operatorul tău"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"Deoarece o aplicație acoperă o solicitare de permisiune, Setările nu îți pot verifica răspunsul."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"Permiți ca <xliff:g id="APP_0">%1$s</xliff:g> să afișeze porțiuni din <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- Poate citi informații din <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Setările ferestrei de mărire"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Atinge ca să deschizi funcțiile de accesibilitate. Personalizează sau înlocuiește butonul în setări.\n\n"<annotation id="link">"Vezi setările"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Mută butonul spre margine pentru a-l ascunde temporar"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Mută în stânga sus"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Mută în dreapta sus"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Mută în stânga jos"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Mută în dreapta jos"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Mută la margine și ascunde"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Mută de la margine și afișează"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"Activează / dezactivează"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Comenzile dispozitivelor"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"Alege aplicația pentru a adăuga comenzi"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"Date mobile"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"Conectat"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"Nu se conectează automat la date mobile"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"Nicio conexiune"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Nu sunt disponibile alte rețele"</string>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index 3d13b2c..b42701e 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Яркость"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Инверсия цветов"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Коррекция цвета"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Пользовательские настройки"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"Готово"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Закрыть"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"Подключено"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"Отключить мобильный Интернет?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"Вы не сможете передавать данные или выходить в Интернет через оператора \"<xliff:g id="CARRIER">%s</xliff:g>\". Интернет будет доступен только по сети Wi-Fi."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"ваш оператор"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"Невозможно принять ваше согласие, поскольку запрос скрыт другим приложением."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"Разрешить приложению \"<xliff:g id="APP_0">%1$s</xliff:g>\" показывать фрагменты приложения \"<xliff:g id="APP_2">%2$s</xliff:g>\"?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"– Ему станут доступны данные из приложения \"<xliff:g id="APP">%1$s</xliff:g>\"."</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Настройка окна лупы"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Нажмите, чтобы открыть спец. возможности. Настройте или замените эту кнопку в настройках.\n\n"<annotation id="link">"Настройки"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Чтобы временно скрыть кнопку, переместите ее к краю экрана"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Перенести в левый верхний угол"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Перенести в правый верхний угол"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Перенести в левый нижний угол"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Перенести в правый нижний угол"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Перенести к краю и скрыть"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Вернуть из-за края и показать"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"включить или отключить"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Управление устройствами"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"Чтобы добавить виджеты управления, выберите приложение"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"Мобильный интернет"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"Подключено"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"Без автоподключения к мобильному интернету"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"Нет подключения к интернету"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Нет других доступных сетей"</string>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index 01be742..bd272c7 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"දීප්තිමත් බව"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"වර්ණ අපවර්තනය"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"වර්ණ නිවැරදි කිරීම"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"පරිශීලක සැකසීම්"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"නිමයි"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"වසන්න"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"සම්බන්ධිත"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"ජංගම දත්ත ක්‍රියාවිරහිත කරන්නද?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"ඔබට <xliff:g id="CARRIER">%s</xliff:g> හරහා දත්ත හෝ අන්තර්ජාලයට පිවිසීමේ හැකියාවක් නැත. අන්තර්ජාලය Wi-Fi හරහා පමණක් ලබා ගත හැකිය."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"ඔබගේ වාහකය"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"යෙදුමක් අවසර ඉල්ලීමක් කරන නිසා, සැකසීම්වලට ඔබගේ ප්‍රතිචාරය සත්‍යාපනය කළ නොහැකිය."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> හට කොටස් <xliff:g id="APP_2">%2$s</xliff:g>ක් පෙන්වීමට ඉඩ දෙන්නද?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- එයට <xliff:g id="APP">%1$s</xliff:g> වෙතින් තොරතුරු කියවිය හැකිය"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"විශාලන කවුළු සැකසීම්"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"ප්‍රවේශ්‍යතා විශේෂාංග විවෘත කිරීමට තට්ටු කරන්න. සැකසීම් තුළ මෙම බොත්තම අභිරුචිකරණය හෝ ප්‍රතිස්ථාපනය කරන්න.\n\n"<annotation id="link">"සැකසීම් බලන්න"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"එය තාවකාලිකව සැඟවීමට බොත්තම දාරයට ගෙන යන්න"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ඉහළ වමට ගෙන යන්න"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"ඉහළ දකුණට ගෙන යන්න"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"පහළ වමට ගෙන යන්න"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"පහළ දකුණට ගෙන යන්න"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"මායිමට ගෙන යන්න සහ සඟවන්න"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"මායිමෙන් පිටට ගන්න සහ පෙන්වන්න"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"ටොගල් කරන්න"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"උපාංග පාලන"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"පාලන එක් කිරීමට යෙදුම තෝරා ගන්න"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"ජංගම දත්ත"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"සම්බන්ධයි"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"ජංගම දත්ත ස්වංක්‍රියව සම්බන්ධ නොවනු ඇත"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"සම්බන්ධතාවයක් නැත"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"ලබා ගත හැකි වෙනත් ජාල නැත"</string>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index 76dce74..a0dd3c3 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Jas"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inverzia farieb"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Úprava farieb"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Používateľské nastavenia"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"Hotovo"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Zavrieť"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"Pripojené"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"Chcete vypnúť mobilné dáta?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"Nebudete mať prístup k dátam ani internetu prostredníctvom operátora <xliff:g id="CARRIER">%s</xliff:g>. Internet bude k dispozícii iba cez Wi‑Fi."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"váš operátor"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"Nastavenia nemôžu overiť vašu odpoveď, pretože určitá aplikácia blokuje žiadosť o povolenie."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"Povoliť aplikácii <xliff:g id="APP_0">%1$s</xliff:g> zobrazovať rezy z aplikácie <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"– Môže čítať informácie z aplikácie <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Nastavenia okna lupy"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Funkcie dostupnosti otvoríte klepnutím. Tlačidlo prispôsobte alebo nahraďte v Nastav.\n\n"<annotation id="link">"Zobraz. nast."</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Ak chcete tlačidlo dočasne skryť, presuňte ho k okraju"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Presunúť doľava nahor"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Presunúť doprava nahor"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Presunúť doľava nadol"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Presunúť doprava nadol"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Presunúť k okraju a skryť"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Presunúť z okraja a zobraziť"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"prepínač"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Ovládanie zariadení"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"Vyberte aplikáciu, ktorej ovládače si chcete pridať"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobilné dáta"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"Pripojené"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"Automatické pripojenie cez mobilné dáta nefunguje"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"Bez pripojenia"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Nie sú k dispozícii žiadne ďalšie siete"</string>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index cb306c2..44108c3 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Svetlost"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Inverzija barv"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Popravljanje barv"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Uporabniške nastavitve"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"Končano"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Zapri"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"Povezava je vzpostavljena"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"Želite izklopiti prenos podatkov v mobilnih omrežjih?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"Prek operaterja »<xliff:g id="CARRIER">%s</xliff:g>« ne boste imeli dostopa do podatkovne povezave ali interneta. Internet bo na voljo samo prek povezave Wi-Fi."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"svojega operaterja"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"Ker aplikacija zakriva zahtevo za dovoljenje, z nastavitvami ni mogoče preveriti vašega odziva."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"Želite dovoliti, da aplikacija <xliff:g id="APP_0">%1$s</xliff:g> prikaže izreze aplikacije <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"– lahko bere podatke v aplikaciji <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Nastavitve okna povečevalnika"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Dotaknite se za funkcije za ljudi s posebnimi potrebami. Ta gumb lahko prilagodite ali zamenjate v nastavitvah.\n\n"<annotation id="link">"Ogled nastavitev"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Če želite gumb začasno skriti, ga premaknite ob rob."</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Premakni zgoraj levo"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Premakni zgoraj desno"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Premakni spodaj levo"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Premakni spodaj desno"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Premakni na rob in skrij"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Premakni z roba in pokaži"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"preklop"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Kontrolniki naprave"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"Izberite aplikacijo za dodajanje kontrolnikov"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"Prenos podatkov v mobilnem omrežju"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"Povezano"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobilna podatkovna povezava ne bo samodejna."</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"Ni povezave"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Nobeno drugo omrežje ni na voljo"</string>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index 9a6d73c..7630261 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Ndriçimi"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Anasjellja e ngjyrës"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Korrigjimi i ngjyrës"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Cilësimet e përdoruesit"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"U krye"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Mbyll"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"I lidhur"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"Të çaktivizohen të dhënat celulare?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"Nuk do të kesh qasje te të dhënat ose interneti nëpërmjet <xliff:g id="CARRIER">%s</xliff:g>. Interneti do të ofrohet vetëm nëpërmjet Wi-Fi."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"operatori yt celular"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"Duke qenë se një aplikacion po bllokon një kërkesë për leje, \"Cilësimet\" nuk mund të verifikojnë përgjigjen tënde."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"Të lejohet <xliff:g id="APP_0">%1$s</xliff:g> që të shfaqë pjesë të <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- Mund të lexojë informacion nga <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Cilësimet e dritares së zmadhimit"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Trokit dhe hap veçoritë e qasshmërisë. Modifiko ose ndërro butonin te \"Cilësimet\".\n\n"<annotation id="link">"Shih cilësimet"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Zhvendose butonin në skaj për ta fshehur përkohësisht"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Zhvendos lart majtas"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Zhvendos lart djathtas"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Zhvendos poshtë majtas"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Zhvendos poshtë djathtas"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Zhvendose te skaji dhe fshihe"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Zhvendose jashtë skajit dhe shfaqe"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"aktivizo/çaktivizo"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Kontrollet e pajisjes"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"Zgjidh aplikacionin për të shtuar kontrollet"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"Të dhënat celulare"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"Lidhur"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"Të dhënat celulare nuk do të lidhen automatikisht"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"Nuk ka lidhje"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Nuk ofrohet asnjë rrjet tjetër"</string>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index 0dff11f..2a7b88f 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Осветљеност"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Инверзија боја"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Корекција боја"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Корисничка подешавања"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"Готово"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Затвори"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"Повезан"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"Желите да искључите мобилне податке?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"Нећете имати приступ подацима или интернету преко мобилног оператера <xliff:g id="CARRIER">%s</xliff:g>. Интернет ће бити доступан само преко WiFi везе."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"мобилни оператер"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"Подешавања не могу да верификују ваш одговор јер апликација скрива захтев за дозволу."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"Желите ли да дозволите апликацији <xliff:g id="APP_0">%1$s</xliff:g> да приказује исечке из апликације <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"– Може да чита податке из апликације <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Подешавања прозора за увећање"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Додирните за функције приступачности. Прилагодите или замените ово дугме у Подешавањима.\n\n"<annotation id="link">"Подешавања"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Померите дугме до ивице да бисте га привремено сакрили"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Премести горе лево"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Премести горе десно"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Премести доле лево"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Премести доле десно"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Премести до ивице и сакриј"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Премести изван ивице и прикажи"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"укључите/искључите"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Контроле уређаја"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"Одаберите апликацију за додавање контрола"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"Мобилни подаци"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"Повезано"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"Није успело аутом. повезивање преко моб. података"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"Веза није успостављена"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Није доступна ниједна друга мрежа"</string>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index c1cdc5b..48654d5 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Ljusstyrka"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Färginvertering"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Färgkorrigering"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Användarinställningar"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"Klart"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Stäng"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"Ansluten"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"Vill du inaktivera mobildata?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"Du kan inte skicka data eller använda internet via <xliff:g id="CARRIER">%s</xliff:g>. Internetanslutning blir bara möjlig via wifi."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"din operatör"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"Svaret kan inte verifieras av Inställningar eftersom en app skymmer en begäran om behörighet."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"Tillåter du att bitar av <xliff:g id="APP_2">%2$s</xliff:g> visas i <xliff:g id="APP_0">%1$s</xliff:g>?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"– Kan läsa information från <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Inställningar för förstoringsfönster"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Tryck för att öppna tillgänglighetsfunktioner. Anpassa/ersätt knappen i Inställningar.\n\n"<annotation id="link">"Inställningar"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Flytta knappen till kanten för att dölja den tillfälligt"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Flytta högst upp till vänster"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Flytta högst upp till höger"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Flytta längst ned till vänster"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Flytta längst ned till höger"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Flytta till kanten och dölj"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Flytta från kanten och visa"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"aktivera och inaktivera"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Enhetsstyrning"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"Välj en app om du vill lägga till snabbkontroller"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobildata"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"Ansluten"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"Du ansluts inte till mobildata automatiskt"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"Ingen anslutning"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Inga andra nätverk är tillgängliga"</string>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index 8ed824e..6bf2bc2 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Ung\'avu"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Ugeuzaji rangi"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Usahihishaji wa rangirangi"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Mipangilio ya mtumiaji"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"Nimemaliza"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Funga"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"Imeunganishwa"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"Ungependa kuzima data ya mtandao wa simu?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"Hutaweza kufikia data au intaneti kupitia <xliff:g id="CARRIER">%s</xliff:g>. Intaneti itapatikana kupitia Wi-Fi pekee."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"mtoa huduma wako"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"Kwa sababu programu nyingine inazuia ombi la ruhusa, hatuwezi kuthibitisha jibu lako katika Mipangilio."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"Ungependa kuruhusu <xliff:g id="APP_0">%1$s</xliff:g> ionyeshe vipengee <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- Inaweza kusoma maelezo kutoka <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Mipangilio ya dirisha la kikuzaji"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Gusa ili ufungue vipengele vya ufikivu. Weka mapendeleo au ubadilishe kitufe katika Mipangilio.\n\n"<annotation id="link">"Angalia mipangilio"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Sogeza kitufe kwenye ukingo ili ukifiche kwa muda"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Sogeza juu kushoto"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Sogeza juu kulia"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Sogeza chini kushoto"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Sogeza chini kulia"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Sogeza kwenye ukingo kisha ufiche"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Sogeza nje ya ukingo kisha uonyeshe"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"geuza"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Vidhibiti vya vifaa"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"Chagua programu ili uweke vidhibiti"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"Data ya mtandao wa simu"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"Imeunganishwa"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"Data ya mtandao wa simu haitaunganishwa kiotomatiki"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"Hakuna muunganisho"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Hakuna mitandao mingine inayopatikana"</string>
diff --git a/packages/SystemUI/res/values-sw600dp-h900dp/dimens.xml b/packages/SystemUI/res/values-sw600dp-h900dp/dimens.xml
new file mode 100644
index 0000000..aab914f
--- /dev/null
+++ b/packages/SystemUI/res/values-sw600dp-h900dp/dimens.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+
+<!-- Intended for wide devices that are currently oriented with a lot of available height,
+     such as tablets. 'hxxxdp' is used instead of 'port' in order to avoid this being applied
+     to wide devices that are shorter in height, like foldables. -->
+<resources>
+    <!-- Space between status view and notification shelf -->
+    <dimen name="keyguard_status_view_bottom_margin">35dp</dimen>
+    <dimen name="keyguard_clock_top_margin">40dp</dimen>
+</resources>
diff --git a/packages/SystemUI/res/values-sw600dp-port/dimens.xml b/packages/SystemUI/res/values-sw600dp-port/dimens.xml
index 347cf29..d9df337 100644
--- a/packages/SystemUI/res/values-sw600dp-port/dimens.xml
+++ b/packages/SystemUI/res/values-sw600dp-port/dimens.xml
@@ -17,8 +17,6 @@
 <resources>
     <dimen name="notification_panel_margin_horizontal">48dp</dimen>
     <dimen name="status_view_margin_horizontal">62dp</dimen>
-    <dimen name="keyguard_clock_top_margin">40dp</dimen>
-    <dimen name="keyguard_status_view_bottom_margin">40dp</dimen>
     <dimen name="bouncer_user_switcher_y_trans">20dp</dimen>
 
     <!-- qs_tiles_page_horizontal_margin should be margin / 2, otherwise full space between two
diff --git a/packages/SystemUI/res/values-sw720dp-h1000dp/dimens.xml b/packages/SystemUI/res/values-sw720dp-h1000dp/dimens.xml
new file mode 100644
index 0000000..97ead01
--- /dev/null
+++ b/packages/SystemUI/res/values-sw720dp-h1000dp/dimens.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+<!-- Intended for wide devices that are currently oriented with a lot of available height,
+     such as tablets. 'hxxxdp' is used instead of 'port' in order to avoid this being applied
+     to wide devices that are shorter in height, like foldables. -->
+<resources>
+    <!-- Space between status view and notification shelf -->
+    <dimen name="keyguard_status_view_bottom_margin">70dp</dimen>
+    <dimen name="keyguard_clock_top_margin">80dp</dimen>
+</resources>
diff --git a/packages/SystemUI/res/values-sw720dp-port/dimens.xml b/packages/SystemUI/res/values-sw720dp-port/dimens.xml
index 3d8da8a..17f82b5 100644
--- a/packages/SystemUI/res/values-sw720dp-port/dimens.xml
+++ b/packages/SystemUI/res/values-sw720dp-port/dimens.xml
@@ -21,8 +21,6 @@
      for different hardware and product builds. -->
 <resources>
     <dimen name="status_view_margin_horizontal">124dp</dimen>
-    <dimen name="keyguard_clock_top_margin">80dp</dimen>
-    <dimen name="keyguard_status_view_bottom_margin">80dp</dimen>
     <dimen name="bouncer_user_switcher_y_trans">200dp</dimen>
 
     <dimen name="large_screen_shade_header_left_padding">24dp</dimen>
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index 9397b6c..b291004 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ஒளிர்வு"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"கலர் இன்வெர்ஷன்"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"கலர் கரெக்‌ஷன்"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"பயனர் அமைப்புகள்"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"முடிந்தது"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"மூடுக"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"இணைக்கப்பட்டது"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"மொபைல் டேட்டாவை ஆஃப் செய்யவா?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"<xliff:g id="CARRIER">%s</xliff:g> மூலம் டேட்டா அல்லது இணையத்தை உங்களால் பயன்படுத்த முடியாது. வைஃபை வழியாக மட்டுமே இணையத்தைப் பயன்படுத்த முடியும்."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"உங்கள் மொபைல் நிறுவனம்"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"அனுமதிக் கோரிக்கையை ஆப்ஸ் மறைப்பதால், அமைப்புகளால் உங்கள் பதிலைச் சரிபார்க்க முடியாது."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> ஆப்ஸை, <xliff:g id="APP_2">%2$s</xliff:g> ஆப்ஸின் விழிப்பூட்டல்களைக் காண்பிக்க அனுமதிக்கவா?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- இது, <xliff:g id="APP">%1$s</xliff:g> பயன்பாட்டிலிருந்து தகவலைப் படிக்கும்"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"சாளரத்தைப் பெரிதாக்கும் கருவிக்கான அமைப்புகள்"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"அணுகல்தன்மை அம்சத்தை திறக்க தட்டவும். அமைப்பில் பட்டனை பிரத்தியேகமாக்கலாம்/மாற்றலாம்.\n\n"<annotation id="link">"அமைப்பில் காண்க"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"பட்டனைத் தற்காலிகமாக மறைக்க ஓரத்திற்கு நகர்த்தும்"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"மேலே இடதுபுறத்திற்கு நகர்த்து"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"மேலே வலதுபுறத்திற்கு நகர்த்து"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"கீழே இடதுபுறத்திற்கு நகர்த்து"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"கீழே வலதுபுறத்திற்கு நகர்த்து"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"ஓரத்திற்கு நகர்த்தி மறை"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"ஓரத்திற்கு நகர்த்தி, காட்டு"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"நிலைமாற்று"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"சாதனக் கட்டுப்பாடுகள்"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"கட்டுப்பாடுகளைச் சேர்க்க வேண்டிய ஆப்ஸைத் தேர்ந்தெடுங்கள்"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"மொபைல் டேட்டா"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"இணைக்கப்பட்டது"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"மொபைல் டேட்டாவுடன் தானாக இணைக்காது"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"இணைப்பு இல்லை"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"வேறு நெட்வொர்க்குகள் எதுவும் கிடைக்கவில்லை"</string>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index f202fae..4c34f45 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ప్రకాశం"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"కలర్ మార్పిడి"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"కలర్ కరెక్షన్"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"యూజర్ సెట్టింగ్‌లు"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"పూర్తయింది"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"మూసివేయి"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"కనెక్ట్ చేయబడినది"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"మొబైల్ డేటాను ఆఫ్ చేయాలా?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"\"<xliff:g id="CARRIER">%s</xliff:g>\" ద్వారా మీకు డేటా లేదా ఇంటర్నెట్‌కు యాక్సెస్ ఉండదు. Wi-Fi ద్వారా మాత్రమే ఇంటర్నెట్ అందుబాటులో ఉంటుంది."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"మీ క్యారియర్"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"అనుమతి రిక్వెస్ట్‌కు ఒక యాప్ అడ్డు తగులుతున్నందున సెట్టింగ్‌లు మీ ప్రతిస్పందనను ధృవీకరించలేకపోయాయి."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_2">%2$s</xliff:g> స్లైస్‌లను చూపించడానికి <xliff:g id="APP_0">%1$s</xliff:g>ని అనుమతించండి?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- ఇది <xliff:g id="APP">%1$s</xliff:g> నుండి సమాచారాన్ని చదువుతుంది"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"మాగ్నిఫయర్ విండో సెట్టింగ్‌లు"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"యాక్సెసిబిలిటీ ఫీచర్‌లను తెరవడానికి ట్యాప్ చేయండి. సెట్టింగ్‌లలో ఈ బటన్‌ను అనుకూలంగా మార్చండి లేదా రీప్లేస్ చేయండి.\n\n"<annotation id="link">"వీక్షణ సెట్టింగ్‌లు"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"తాత్కాలికంగా దానిని దాచడానికి బటన్‌ను చివరకు తరలించండి"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ఎగువ ఎడమ వైపునకు తరలించు"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"ఎగువ కుడి వైపునకు తరలించు"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"దిగువ ఎడమ వైపునకు తరలించు"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"దిగువ కుడి వైపునకు తరలించు"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"అంచుకు తరలించి దాచండి"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"అంచుని తరలించి చూపించు"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"టోగుల్ చేయి"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"డివైజ్ కంట్రోల్స్"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"కంట్రోల్స్‌ను యాడ్ చేయడానికి యాప్‌ను ఎంచుకోండి"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"మొబైల్ డేటా"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"కనెక్ట్ చేయబడింది"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"మొబైల్ డేటా ఆటోమెటిక్‌గా కనెక్ట్ అవ్వదు"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"కనెక్షన్ లేదు"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"ఇతర నెట్‌వర్క్‌లేవీ అందుబాటులో లేవు"</string>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index f216437..5de908c 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -248,7 +248,7 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"ความสว่าง"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"การกลับสี"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"การแก้สี"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"การตั้งค่าผู้ใช้"</string>
+    <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"จัดการผู้ใช้"</string>
     <string name="quick_settings_done" msgid="2163641301648855793">"เสร็จสิ้น"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"ปิด"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"เชื่อมต่อ"</string>
@@ -727,6 +727,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"ปิดอินเทอร์เน็ตมือถือไหม"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"คุณจะใช้เน็ตมือถือหรืออินเทอร์เน็ตผ่าน \"<xliff:g id="CARRIER">%s</xliff:g>\" ไม่ได้ แต่จะใช้ผ่าน Wi-Fi ได้เท่านั้น"</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"ผู้ให้บริการของคุณ"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"เนื่องจากแอปหนึ่งได้บดบังคำขอสิทธิ์ ระบบจึงไม่สามารถยืนยันคำตอบของคุณสำหรับการตั้งค่าได้"</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"อนุญาตให้ <xliff:g id="APP_0">%1$s</xliff:g> แสดงส่วนต่างๆ ของ <xliff:g id="APP_2">%2$s</xliff:g>"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- อ่านข้อมูลจาก <xliff:g id="APP">%1$s</xliff:g> ได้"</string>
@@ -785,12 +793,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"การตั้งค่าหน้าต่างแว่นขยาย"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"แตะเพื่อเปิดฟีเจอร์การช่วยเหลือพิเศษ ปรับแต่งหรือแทนที่ปุ่มนี้ในการตั้งค่า\n\n"<annotation id="link">"ดูการตั้งค่า"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"ย้ายปุ่มไปที่ขอบเพื่อซ่อนชั่วคราว"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"ย้ายไปด้านซ้ายบน"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"ย้ายไปด้านขวาบน"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"ย้ายไปด้านซ้ายล่าง"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"ย้ายไปด้านขาวล่าง"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"ย้ายไปที่ขอบและซ่อน"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"ย้ายออกจากขอบและแสดง"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"สลับ"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"ระบบควบคุมอุปกรณ์"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"เลือกแอปเพื่อเพิ่มตัวควบคุม"</string>
@@ -933,6 +947,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"อินเทอร์เน็ตมือถือ"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"เชื่อมต่อแล้ว"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"อินเทอร์เน็ตมือถือจะไม่เชื่อมต่ออัตโนมัติ"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"ไม่มีการเชื่อมต่อ"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"ไม่มีเครือข่ายอื่นๆ ที่พร้อมใช้งาน"</string>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index f1acf43..f47b906 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -248,7 +248,7 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Brightness"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Pag-invert ng kulay"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Pagtatama ng kulay"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Mga setting ng user"</string>
+    <string name="quick_settings_more_user_settings" msgid="7634653308485206306">"Pamahalaan ang mga user"</string>
     <string name="quick_settings_done" msgid="2163641301648855793">"Tapos na"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Isara"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"Nakakonekta"</string>
@@ -727,6 +727,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"I-off ang mobile data?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"Hindi ka magkaka-access sa data o internet sa pamamagitan ng <xliff:g id="CARRIER">%s</xliff:g>. Available lang ang internet sa pamamagitan ng Wi-Fi."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"ang iyong carrier"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"Hindi ma-verify ng Mga Setting ang iyong tugon dahil may app na tumatakip sa isang kahilingan sa pagpapahintulot."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"Payagan ang <xliff:g id="APP_0">%1$s</xliff:g> na ipakita ang mga slice ng <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- Nakakabasa ito ng impormasyon mula sa <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -785,12 +793,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Mga setting ng window ng magnifier"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"I-tap, buksan mga feature ng accessibility. I-customize o palitan button sa Mga Setting.\n\n"<annotation id="link">"Tingnan ang mga setting"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Ilipat ang button sa gilid para pansamantala itong itago"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Ilipat sa kaliwa sa itaas"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Ilipat sa kanan sa itaas"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Ilipat sa kaliwa sa ibaba"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Ilipat sa kanan sa ibaba"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Ilipat sa sulok at itago"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Alisin sa sulok at ipakita"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"i-toggle"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Mga kontrol ng device"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"Pumili ng app para magdagdag ng mga kontrol"</string>
@@ -933,6 +947,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobile data"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"Nakakonekta"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"Hindi awtomatikong kokonekta ang mobile data"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"Walang koneksyon"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Walang available na iba pang network"</string>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index 7b52a41..17dfa48 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Parlaklık"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Rengi ters çevirme"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Renk düzeltme"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Kullanıcı ayarları"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"Bitti"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Kapat"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"Bağlı"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"Mobil veri kapatılsın mı?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"<xliff:g id="CARRIER">%s</xliff:g> üzerinden veri veya internet erişiminiz olmayacak. İnternet yalnızca kablosuz bağlantı üzerinden kullanılabilecek."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"operatörünüz"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"Bir uygulama bir izin isteğinin anlaşılmasını engellediğinden, Ayarlar, yanıtınızı doğrulayamıyor."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> uygulamasının, <xliff:g id="APP_2">%2$s</xliff:g> dilimlerini göstermesine izin verilsin mi?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- <xliff:g id="APP">%1$s</xliff:g> uygulamasından bilgileri okuyabilir"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Büyüteç penceresi ayarları"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Erişilebilirlik özelliklerini açmak için dokunun. Bu düğmeyi Ayarlar\'dan özelleştirin veya değiştirin.\n\n"<annotation id="link">"Ayarları göster"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Düğmeyi geçici olarak gizlemek için kenara taşıyın"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Sol üste taşı"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Sağ üste taşı"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Sol alta taşı"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Sağ alta taşı"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Kenara taşıyıp gizle"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Kenarın dışına taşıyıp göster"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"değiştir"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Cihaz denetimleri"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"Denetim eklemek için uygulama seçin"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobil veri"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"Bağlı"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobil veri otomatik olarak bağlanmıyor"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"Bağlantı yok"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Kullanılabilir başka ağ yok"</string>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index 6bd9e30..ff1b594 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Яскравість"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Інверсія кольорів"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Корекція кольору"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Налаштування користувача"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"Готово"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Закрити"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"Під’єднано"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"Вимкнути мобільний Інтернет?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"Ви не матимете доступу до даних чи Інтернету через оператора <xliff:g id="CARRIER">%s</xliff:g>. Інтернет буде доступний лише через Wi-Fi."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"ваш оператор"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"Не вдається підтвердити вашу відповідь у налаштуваннях, оскільки інший додаток заступає запит на дозвіл."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"Дозволити додатку <xliff:g id="APP_0">%1$s</xliff:g> показувати фрагменти додатка <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- Має доступ до інформації з додатка <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Налаштування розміру лупи"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Кнопка спеціальних можливостей. Змініть або замініть її в Налаштуваннях.\n\n"<annotation id="link">"Переглянути налаштування"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Щоб тимчасово сховати кнопку, перемістіть її на край екрана"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Перемістити ліворуч угору"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Перемістити праворуч угору"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Перемістити ліворуч униз"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Перемістити праворуч униз"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Перемістити до краю, приховати"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Перемістити від краю, показати"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"перемкнути"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Керування пристроями"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"Виберіть, для якого додатка налаштувати елементи керування"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"Мобільний трафік"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"Підключено"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"Мобільний Інтернет не підключатиметься автоматично"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"Немає з\'єднання"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Інші мережі недоступні"</string>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index ec382b2..10e0e68 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"چمکیلا پن"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"رنگوں کی تقلیب"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"رنگ کی اصلاح"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"صارف کی ترتیبات"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"ہو گیا"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"بند کریں"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"مربوط"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"موبائل ڈیٹا آف کریں؟"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"‏آپ کو <xliff:g id="CARRIER">%s</xliff:g> کے ذریعے ڈیٹا یا انٹرنیٹ تک رسائی حاصل نہیں ہوگی۔ انٹرنیٹ صرف Wi-Fi کے ذریعے دستیاب ہوگا۔"</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"آپ کا کریئر"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"چونکہ ایک ایپ اجازت کی درخواست کو مبہم کر رہی ہے، لہذا ترتیبات آپ کے جواب کی توثیق نہیں کر سکتی ہیں۔"</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> کو <xliff:g id="APP_2">%2$s</xliff:g> کے سلائسز دکھانے کی اجازت دیں؟"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- یہ <xliff:g id="APP">%1$s</xliff:g> کی معلومات پڑھ سکتا ہے"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"میگنیفائر ونڈو کی ترتیبات"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"ایکسیسبیلٹی خصوصیات کھولنے کے لیے تھپتھپائیں۔ ترتیبات میں اس بٹن کو حسب ضرورت بنائیں یا تبدیل کریں۔\n\n"<annotation id="link">"ترتیبات ملاحظہ کریں"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"عارضی طور پر بٹن کو چھپانے کے لئے اسے کنارے پر لے جائیں"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"اوپر بائیں جانب لے جائیں"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"اوپر دائیں جانب لے جائيں"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"نیچے بائیں جانب لے جائیں"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"نیچے دائیں جانب لے جائیں"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"‏EDGE پر لے جائیں اور چھپائیں"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"‏EDGE اور شو سے باہر منتقل کریں"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"ٹوگل کریں"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"آلہ کے کنٹرولز"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"کنٹرولز شامل کرنے کے لیے ایپ منتخب کریں"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"موبائل ڈیٹا"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="NETWORKMODE">%2$s</xliff:g> / <xliff:g id="STATE">%1$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"منسلک ہے"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"موبائل ڈیٹا خودکار طور پر منسلک نہیں ہوگا"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"کوئی کنکشن نہیں"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"کوئی دوسرا نیٹ ورک دستیاب نہیں ہے"</string>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index a666432..184044d 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Yorqinlik"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Ranglarni akslantirish"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Ranglarni tuzatish"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Foydalanuvchi sozlamalari"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"Tayyor"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Yopish"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"Ulangan"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"Mobil internet uzilsinmi?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"<xliff:g id="CARRIER">%s</xliff:g> orqali internetdan foydalana olmaysiz. Internet faqat Wi-Fi orqali ishlaydi."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"aloqa operatoringiz"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"Ilova ruxsatnoma so‘roviga xalaqit qilayotgani tufayli, “Sozlamalar” ilovasi javobingizni tekshira olmaydi."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"<xliff:g id="APP_0">%1$s</xliff:g> ilovasiga <xliff:g id="APP_2">%2$s</xliff:g> ilovasidan fragmentlar ko‘rsatishga ruxsat berilsinmi?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"– <xliff:g id="APP">%1$s</xliff:g> ma’lumotlarini o‘qiy oladi"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Lupa oynasi sozlamalari"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Maxsus imkoniyatlarni ochish uchun bosing Sozlamalardan moslay yoki almashtira olasiz.\n\n"<annotation id="link">"Sozlamalar"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Vaqtinchalik berkitish uchun tugmani qirra tomon suring"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Yuqori chapga surish"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Yuqori oʻngga surish"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Quyi chapga surish"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Quyi oʻngga surish"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Chetiga olib borish va yashirish"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Chetidan qaytarish va koʻrsatish"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"oʻzgartirish"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Qurilmalarni boshqarish"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"Boshqaruv elementlarini kiritish uchun ilovani tanlang"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"Mobil internet"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"Ulandi"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"Mobil internetga avtomatik ulanmaydi"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"Internetga ulanmagansiz"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Boshqa tarmoqlar mavjud emas"</string>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index 21200ca..8ea3408 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Độ sáng"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Đảo màu"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Chỉnh màu"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Cài đặt người dùng"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"Xong"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Đóng"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"Đã kết nối"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"Tắt dữ liệu di động?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"Bạn sẽ không có quyền sử dụng dữ liệu hoặc truy cập Internet thông qua chế độ <xliff:g id="CARRIER">%s</xliff:g>. Bạn chỉ có thể truy cập Internet thông qua Wi-Fi."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"nhà mạng của bạn"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"Vì ứng dụng đang che khuất yêu cầu cấp quyền nên Cài đặt không thể xác minh câu trả lời của bạn."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"Cho phép <xliff:g id="APP_0">%1$s</xliff:g> hiển thị các lát của <xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- Có thể đọc thông tin từ <xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Chế độ cài đặt cửa sổ phóng to"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Nhấn để mở bộ tính năng hỗ trợ tiếp cận. Tuỳ chỉnh/thay thế nút này trong phần Cài đặt.\n\n"<annotation id="link">"Xem chế độ cài đặt"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Di chuyển nút sang cạnh để ẩn nút tạm thời"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Chuyển lên trên cùng bên trái"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Chuyển lên trên cùng bên phải"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Chuyển tới dưới cùng bên trái"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Chuyển tới dưới cùng bên phải"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Chuyển đến cạnh và ẩn"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Chuyển ra xa cạnh và hiển thị"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"bật/tắt"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Điều khiển thiết bị"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"Chọn ứng dụng để thêm các tùy chọn điều khiển"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"Dữ liệu di động"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"Đã kết nối"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"Dữ liệu di động sẽ không tự động kết nối"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"Không có kết nối mạng"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Không có mạng nào khác"</string>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index 83508a1..1cead42 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"亮度"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"颜色反转"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"色彩校正"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"用户设置"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"完成"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"关闭"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"已连接"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"要关闭移动数据网络吗?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"您将无法通过<xliff:g id="CARRIER">%s</xliff:g>使用移动数据或互联网,只能通过 WLAN 连接到互联网。"</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"您的运营商"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"由于某个应用遮挡了权限请求界面,因此“设置”应用无法验证您的回应。"</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"要允许“<xliff:g id="APP_0">%1$s</xliff:g>”显示“<xliff:g id="APP_2">%2$s</xliff:g>”图块吗?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- 可以读取“<xliff:g id="APP">%1$s</xliff:g>”中的信息"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"放大镜窗口设置"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"点按即可打开无障碍功能。您可在“设置”中自定义或更换此按钮。\n\n"<annotation id="link">"查看设置"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"将按钮移到边缘,即可暂时将其隐藏"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"移至左上角"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"移至右上角"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"移至左下角"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"移至右下角"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"移至边缘并隐藏"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"移至边缘以外并显示"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"开启/关闭"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"设备控制器"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"选择要添加控制器的应用"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"移动数据网络"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"已连接"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"系统将不会自动连接到移动数据网络"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"无网络连接"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"没有其他可用网络"</string>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index 28420b3..cb90614 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"亮度"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"色彩反轉"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"色彩校正"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"使用者設定"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"完成"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"關閉"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"已連線"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"要關閉流動數據嗎?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"您無法透過「<xliff:g id="CARRIER">%s</xliff:g>」使用流動數據或互聯網。如要使用互聯網,您必須連接 Wi-Fi。"</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"您的流動網絡供應商"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"由於某個應用程式已阻擋權限要求畫面,因此「設定」應用程式無法驗證您的回應。"</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"要允許「<xliff:g id="APP_0">%1$s</xliff:g>」顯示「<xliff:g id="APP_2">%2$s</xliff:g>」的快訊嗎?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- 可以讀取「<xliff:g id="APP">%1$s</xliff:g>」中的資料"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"放大鏡視窗設定"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"㩒一下就可以開無障礙功能。喺「設定」度自訂或者取代呢個按鈕。\n\n"<annotation id="link">"查看設定"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"將按鈕移到邊緣即可暫時隱藏"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"移去左上方"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"移去右上方"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"移到左下方"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"移去右下方"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"移到邊緣並隱藏"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"從邊緣移出並顯示"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"切換"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"裝置控制"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"選擇要新增控制項的應用程式"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"流動數據"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"已連線"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"不會自動連線至流動數據"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"沒有連線"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"沒有可用的其他網絡"</string>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index 8a1a9e2..ccad340 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"亮度"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"色彩反轉"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"色彩校正"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"使用者設定"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"完成"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"關閉"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"已連線"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"要關閉行動數據嗎?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"你將無法透過「<xliff:g id="CARRIER">%s</xliff:g>」使用行動數據或網際網路。你只能透過 Wi-Fi 使用網際網路。"</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"你的電信業者"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"由於某個應用程式覆蓋了權限要求畫面,因此「設定」應用程式無法驗證你的回應。"</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"要允許「<xliff:g id="APP_0">%1$s</xliff:g>」顯示「<xliff:g id="APP_2">%2$s</xliff:g>」的區塊嗎?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- 它可以讀取「<xliff:g id="APP">%1$s</xliff:g>」的資訊"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"放大鏡視窗設定"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"輕觸即可開啟無障礙功能。你可以前往「設定」自訂或更換這個按鈕。\n\n"<annotation id="link">"查看設定"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"將按鈕移到邊緣處即可暫時隱藏"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"移到左上方"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"移到右上方"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"移到左下方"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"移到右下方"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"移到邊緣並隱藏"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"從邊緣移出並顯示"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"切換"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"裝置控制"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"選擇應用程式以新增控制項"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"行動數據"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g>/<xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"已連線"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"系統將不會自動使用行動數據連線"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"沒有網路連線"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"沒有可用的其他網路"</string>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index 9b376f0..b346494 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -248,7 +248,8 @@
     <string name="quick_settings_brightness_dialog_title" msgid="4980669966716685588">"Ukugqama"</string>
     <string name="quick_settings_inversion_label" msgid="3501527749494755688">"Ukuguqulwa kombala"</string>
     <string name="quick_settings_color_correction_label" msgid="5636617913560474664">"Ukulungiswa kombala"</string>
-    <string name="quick_settings_more_user_settings" msgid="1064187451100861954">"Amasethingi womsebenzisi"</string>
+    <!-- no translation found for quick_settings_more_user_settings (7634653308485206306) -->
+    <skip />
     <string name="quick_settings_done" msgid="2163641301648855793">"Kwenziwe"</string>
     <string name="quick_settings_close_user_panel" msgid="5599724542275896849">"Vala"</string>
     <string name="quick_settings_connected" msgid="3873605509184830379">"Ixhunyiwe"</string>
@@ -727,6 +728,14 @@
     <string name="mobile_data_disable_title" msgid="5366476131671617790">"Vala idatha yeselula?"</string>
     <string name="mobile_data_disable_message" msgid="8604966027899770415">"Ngeke ube nokufinyelela kudatha noma ku-inthanethi nge-<xliff:g id="CARRIER">%s</xliff:g>. I-inthanethi izotholakala kuphela nge-Wi-Fi."</string>
     <string name="mobile_data_disable_message_default_carrier" msgid="6496033312431658238">"inkampani yakho yenethiwekhi"</string>
+    <!-- no translation found for auto_data_switch_disable_title (5146527155665190652) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_disable_message (5885533647399535852) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_negative_button (2370876875999891444) -->
+    <skip />
+    <!-- no translation found for auto_data_switch_dialog_positive_button (8531782041263087564) -->
+    <skip />
     <string name="touch_filtered_warning" msgid="8119511393338714836">"Ngoba uhlelo lokusebenza lusitha isicelo semvume, Izilungiselelo azikwazi ukuqinisekisa impendulo yakho."</string>
     <string name="slice_permission_title" msgid="3262615140094151017">"Vumela i-<xliff:g id="APP_0">%1$s</xliff:g> ukuthi ibonise izingcezu ze-<xliff:g id="APP_2">%2$s</xliff:g>?"</string>
     <string name="slice_permission_text_1" msgid="6675965177075443714">"- Ingafunda ulwazi kusukela ku-<xliff:g id="APP">%1$s</xliff:g>"</string>
@@ -785,12 +794,18 @@
     <string name="accessibility_magnification_magnifier_window_settings" msgid="2834685072221468434">"Amasethingi ewindi lesikhulisi"</string>
     <string name="accessibility_floating_button_migration_tooltip" msgid="5217151214439341902">"Thepha ukuze uvule izakhi zokufinyelela. Enza ngendlela oyifisayo noma shintsha le nkinobho Kumasethingi.\n\n"<annotation id="link">"Buka amasethingi"</annotation></string>
     <string name="accessibility_floating_button_docking_tooltip" msgid="6814897496767461517">"Hambisa inkinobho onqenqemeni ukuze uyifihle okwesikhashana"</string>
+    <!-- no translation found for accessibility_floating_button_undo (511112888715708241) -->
+    <skip />
+    <!-- no translation found for accessibility_floating_button_undo_message_text (3044079592757099698) -->
+    <skip />
     <string name="accessibility_floating_button_action_move_top_left" msgid="6253520703618545705">"Hamba phezulu kwesokunxele"</string>
     <string name="accessibility_floating_button_action_move_top_right" msgid="6106225581993479711">"Hamba phezulu ngakwesokudla"</string>
     <string name="accessibility_floating_button_action_move_bottom_left" msgid="8063394111137429725">"Hamba phansi ngakwesokunxele"</string>
     <string name="accessibility_floating_button_action_move_bottom_right" msgid="6196904373227440500">"Hamba phansi ngakwesokudla"</string>
     <string name="accessibility_floating_button_action_move_to_edge_and_hide_to_half" msgid="662401168245782658">"Hamba onqenqemeni ufihle"</string>
     <string name="accessibility_floating_button_action_move_out_edge_and_show" msgid="8354760891651663326">"Phuma onqenqemeni ubonise"</string>
+    <!-- no translation found for accessibility_floating_button_action_remove_menu (6730432848162552135) -->
+    <skip />
     <string name="accessibility_floating_button_action_double_tap_to_toggle" msgid="7976492639670692037">"guqula"</string>
     <string name="quick_controls_title" msgid="6839108006171302273">"Izilawuli zezinsiza"</string>
     <string name="controls_providers_title" msgid="6879775889857085056">"Khetha uhlelo lokusebenza ukwengeza izilawuli"</string>
@@ -933,6 +948,10 @@
     <string name="mobile_data_settings_title" msgid="3955246641380064901">"Idatha yeselula"</string>
     <string name="preference_summary_default_combination" msgid="8453246369903749670">"<xliff:g id="STATE">%1$s</xliff:g> / <xliff:g id="NETWORKMODE">%2$s</xliff:g>"</string>
     <string name="mobile_data_connection_active" msgid="944490013299018227">"Ixhunyiwe"</string>
+    <!-- no translation found for mobile_data_temp_connection_active (4590222725908806824) -->
+    <skip />
+    <!-- no translation found for mobile_data_poor_connection (819617772268371434) -->
+    <skip />
     <string name="mobile_data_off_summary" msgid="3663995422004150567">"Idatha yeselula ngeke ikwazi ukuxhuma ngokuzenzekelayo"</string>
     <string name="mobile_data_no_connection" msgid="1713872434869947377">"Alukho uxhumano"</string>
     <string name="non_carrier_network_unavailable" msgid="770049357024492372">"Awekho amanye amanethiwekhi atholakalayo"</string>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index 93982cb..ce9829b 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -743,6 +743,17 @@
 
     <integer name="complicationRestoreMs">1000</integer>
 
+    <!-- Duration in milliseconds of the dream in un-blur animation. -->
+    <integer name="config_dreamOverlayInBlurDurationMs">249</integer>
+    <!-- Delay in milliseconds of the dream in un-blur animation. -->
+    <integer name="config_dreamOverlayInBlurDelayMs">133</integer>
+    <!-- Duration in milliseconds of the dream in complications fade-in animation. -->
+    <integer name="config_dreamOverlayInComplicationsDurationMs">282</integer>
+    <!-- Delay in milliseconds of the dream in top complications fade-in animation. -->
+    <integer name="config_dreamOverlayInTopComplicationsDelayMs">216</integer>
+    <!-- Delay in milliseconds of the dream in bottom complications fade-in animation. -->
+    <integer name="config_dreamOverlayInBottomComplicationsDelayMs">299</integer>
+
     <!-- Icons that don't show in a collapsed non-keyguard statusbar -->
     <string-array name="config_collapsed_statusbar_icon_blocklist" translatable="false">
         <item>@*android:string/status_bar_volume</item>
@@ -783,4 +794,8 @@
         <item>@color/dream_overlay_aqi_very_unhealthy</item>
         <item>@color/dream_overlay_aqi_hazardous</item>
     </integer-array>
+
+    <!-- Whether the device should display hotspot UI. If true, UI will display only when tethering
+         is available. If false, UI will never show regardless of tethering availability" -->
+    <bool name="config_show_wifi_tethering">true</bool>
 </resources>
diff --git a/packages/SystemUI/res/values/integers.xml b/packages/SystemUI/res/values/integers.xml
index e30d441..8d44315 100644
--- a/packages/SystemUI/res/values/integers.xml
+++ b/packages/SystemUI/res/values/integers.xml
@@ -35,4 +35,6 @@
     <!-- Percentage of displacement for items in QQS to guarantee matching with bottom of clock at
          fade_out_complete_frame -->
     <dimen name="percent_displacement_at_fade_out" format="float">0.1066</dimen>
+
+    <integer name="qs_carrier_max_em">7</integer>
 </resources>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index b325c56..d2deb4f 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -783,6 +783,62 @@
         Microphone and camera available
     </string>
 
+    <!--- Title of dialog triggered if the microphone sensor privacy changed its state to unblocked. [CHAR LIMIT=NONE] -->
+    <string name="sensor_privacy_mic_turned_on_dialog_title">
+        Microphone turned on
+    </string>
+
+    <!--- Title of dialog triggered if the microphone sensor privacy changed its state to blocked. [CHAR LIMIT=NONE] -->
+    <string name="sensor_privacy_mic_turned_off_dialog_title">
+        Microphone turned off
+    </string>
+
+    <!--- Content of dialog triggered if the microphone sensor privacy changed its state to unblocked. [CHAR LIMIT=NONE] -->
+    <string name="sensor_privacy_mic_unblocked_dialog_content">
+        Microphone is enabled for all apps and services.
+    </string>
+
+    <!--- Content of dialog triggered if the microphone sensor privacy changed its state to blocked.
+        and there are no active exceptions for explicit user interaction [CHAR LIMIT=NONE] -->
+    <string name="sensor_privacy_mic_blocked_no_exception_dialog_content">
+        Microphone access is disabled for all apps and services.
+        You can enable microphone access in Settings &gt; Privacy &gt; Microphone.
+    </string>
+
+    <!--- Content of dialog triggered if the microphone sensor privacy changed its state to blocked.
+        and there are active exceptions for explicit user interaction [CHAR LIMIT=NONE] -->
+    <string name="sensor_privacy_mic_blocked_with_exception_dialog_content">
+        Microphone access is disabled for all apps and services.
+        You can change this in Settings &gt; Privacy &gt; Microphone.
+    </string>
+
+    <!--- Title of dialog triggered if the camera sensor privacy changed its state to unblocked. [CHAR LIMIT=NONE] -->
+    <string name="sensor_privacy_camera_turned_on_dialog_title">
+        Camera turned on
+    </string>
+
+    <!--- Title of dialog triggered if the camera sensor privacy changed its state to blocked. [CHAR LIMIT=NONE] -->
+    <string name="sensor_privacy_camera_turned_off_dialog_title">
+        Camera turned off
+    </string>
+
+    <!--- Content of dialog triggered if the camera sensor privacy changed its state to unblocked. [CHAR LIMIT=NONE] -->
+    <string name="sensor_privacy_camera_unblocked_dialog_content">
+        Camera is enabled for all apps and services.
+    </string>
+
+    <!--- Content of dialog triggered if the camera sensor privacy changed its state to blocked. [CHAR LIMIT=NONE] -->
+    <string name="sensor_privacy_camera_blocked_dialog_content">
+        Camera access is disabled for all apps and services.
+    </string>
+
+    <!--- Content of dialog triggered if the microphone sensor privacy changed its state to blocked.
+        and there are active exceptions for explicit user interaction [CHAR LIMIT=NONE] -->
+    <string name="sensor_privacy_htt_blocked_dialog_content">To use the microphone button, enable microphone access in Settings.</string>
+
+    <!-- Sensor privacy dialog: Button to open system settings [CHAR LIMIT=50] -->
+    <string name="sensor_privacy_dialog_open_settings">Open settings.</string>
+
     <!-- Default name for the media device shown in the output switcher when the name is not available [CHAR LIMIT=30] -->
     <string name="media_seamless_other_device">Other device</string>
 
diff --git a/packages/SystemUI/res/xml/media_session_collapsed.xml b/packages/SystemUI/res/xml/media_session_collapsed.xml
index 9115d42..148e5ec 100644
--- a/packages/SystemUI/res/xml/media_session_collapsed.xml
+++ b/packages/SystemUI/res/xml/media_session_collapsed.xml
@@ -34,6 +34,16 @@
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintBottom_toBottomOf="parent" />
 
+    <!-- Touch ripple must have the same constraint as the album art. -->
+    <Constraint
+        android:id="@+id/touch_ripple_view"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/qs_media_session_height_collapsed"
+        app:layout_constraintStart_toStartOf="@+id/album_art"
+        app:layout_constraintEnd_toEndOf="@+id/album_art"
+        app:layout_constraintTop_toTopOf="@+id/album_art"
+        app:layout_constraintBottom_toBottomOf="@+id/album_art" />
+
     <Constraint
         android:id="@+id/header_title"
         android:layout_width="wrap_content"
diff --git a/packages/SystemUI/res/xml/media_session_expanded.xml b/packages/SystemUI/res/xml/media_session_expanded.xml
index 522dc68..ac484d7 100644
--- a/packages/SystemUI/res/xml/media_session_expanded.xml
+++ b/packages/SystemUI/res/xml/media_session_expanded.xml
@@ -27,6 +27,16 @@
         app:layout_constraintTop_toTopOf="parent"
         app:layout_constraintBottom_toBottomOf="parent" />
 
+    <!-- Touch ripple must have the same constraint as the album art. -->
+    <Constraint
+        android:id="@+id/touch_ripple_view"
+        android:layout_width="match_parent"
+        android:layout_height="@dimen/qs_media_session_height_expanded"
+        app:layout_constraintStart_toStartOf="@+id/album_art"
+        app:layout_constraintEnd_toEndOf="@+id/album_art"
+        app:layout_constraintTop_toTopOf="@+id/album_art"
+        app:layout_constraintBottom_toBottomOf="@+id/album_art" />
+
     <Constraint
         android:id="@+id/header_title"
         android:layout_width="wrap_content"
diff --git a/packages/SystemUI/shared/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java b/packages/SystemUI/shared/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java
index 8aa3aba..a14f971 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/navigationbar/buttons/KeyButtonRipple.java
@@ -291,8 +291,10 @@
     }
 
     private void endAnimations(String reason, boolean cancel) {
-        Trace.beginSection("KeyButtonRipple.endAnim: reason=" + reason + " cancel=" + cancel);
-        Trace.endSection();
+        if (Trace.isEnabled()) {
+            Trace.instant(Trace.TRACE_TAG_APP,
+                    "KeyButtonRipple.endAnim: reason=" + reason + " cancel=" + cancel);
+        }
         mVisible = false;
         mTmpArray.addAll(mRunningAnimations);
         int size = mTmpArray.size();
@@ -502,20 +504,23 @@
 
         @Override
         public void onAnimationStart(Animator animation) {
-            Trace.beginSection("KeyButtonRipple.start." + mName);
-            Trace.endSection();
+            if (Trace.isEnabled()) {
+                Trace.instant(Trace.TRACE_TAG_APP, "KeyButtonRipple.start." + mName);
+            }
         }
 
         @Override
         public void onAnimationCancel(Animator animation) {
-            Trace.beginSection("KeyButtonRipple.cancel." + mName);
-            Trace.endSection();
+            if (Trace.isEnabled()) {
+                Trace.instant(Trace.TRACE_TAG_APP, "KeyButtonRipple.cancel." + mName);
+            }
         }
 
         @Override
         public void onAnimationEnd(Animator animation) {
-            Trace.beginSection("KeyButtonRipple.end." + mName);
-            Trace.endSection();
+            if (Trace.isEnabled()) {
+                Trace.instant(Trace.TRACE_TAG_APP, "KeyButtonRipple.end." + mName);
+            }
         }
     }
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt
index da1d233..ca780c8 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -188,13 +188,10 @@
         dozeFraction: Float,
         foldFraction: Float,
     ) : ClockAnimations {
-        private var foldState = AnimationState(0f)
-        private var dozeState = AnimationState(0f)
+        private val dozeState = AnimationState(dozeFraction)
+        private val foldState = AnimationState(foldFraction)
 
         init {
-            dozeState = AnimationState(dozeFraction)
-            foldState = AnimationState(foldFraction)
-
             if (foldState.isActive) {
                 clocks.forEach { it.animateFoldAppear(false) }
             } else {
@@ -235,8 +232,11 @@
     private class AnimationState(
         var fraction: Float,
     ) {
-        var isActive: Boolean = fraction < 0.5f
+        var isActive: Boolean = fraction > 0.5f
         fun update(newFraction: Float): Pair<Boolean, Boolean> {
+            if (newFraction == fraction) {
+                return Pair(isActive, false)
+            }
             val wasActive = isActive
             val hasJumped =
                 (fraction == 0f && newFraction == 1f) || (fraction == 1f && newFraction == 0f)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotifPanelEventsModule.java b/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/shared/model/KeyguardQuickAffordanceSlots.kt
similarity index 60%
copy from packages/SystemUI/src/com/android/systemui/shade/NotifPanelEventsModule.java
copy to packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/shared/model/KeyguardQuickAffordanceSlots.kt
index 6772384..2dc7a28 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotifPanelEventsModule.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/keyguard/shared/model/KeyguardQuickAffordanceSlots.kt
@@ -12,19 +12,16 @@
  * 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.shade;
+package com.android.systemui.shared.keyguard.shared.model
 
-import com.android.systemui.dagger.SysUISingleton;
-
-import dagger.Binds;
-import dagger.Module;
-
-/** Provides a {@link NotifPanelEvents} in {@link SysUISingleton} scope. */
-@Module
-public abstract class NotifPanelEventsModule {
-    @Binds
-    abstract NotifPanelEvents bindPanelEvents(
-            NotificationPanelViewController.PanelEventsEmitter impl);
+/**
+ * Collection of all supported "slots", placements where keyguard quick affordances can appear on
+ * the lock screen.
+ */
+object KeyguardQuickAffordanceSlots {
+    const val SLOT_ID_BOTTOM_START = "bottom_start"
+    const val SLOT_ID_BOTTOM_END = "bottom_end"
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
index 4613e8b..bfbe88c 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/IOverviewProxy.aidl
@@ -20,6 +20,7 @@
 import android.graphics.Region;
 import android.os.Bundle;
 import android.view.MotionEvent;
+import android.view.SurfaceControl;
 import com.android.systemui.shared.recents.ISystemUiProxy;
 
 oneway interface IOverviewProxy {
@@ -44,12 +45,6 @@
     void onOverviewHidden(boolean triggeredFromAltTab, boolean triggeredFromHomeKey) = 8;
 
     /**
-     * Sent when there was an action on one of the onboarding tips view.
-     * TODO: Move this implementation to SystemUI completely
-     */
-    void onTip(int actionType, int viewType) = 10;
-
-    /**
      * Sent when device assistant changes its default assistant whether it is available or not.
      */
     void onAssistantAvailable(boolean available) = 13;
@@ -60,13 +55,6 @@
     void onAssistantVisibilityChanged(float visibility) = 14;
 
     /**
-     * Sent when back is triggered.
-     * TODO: Move this implementation to SystemUI completely
-     */
-    void onBackAction(boolean completed, int downX, int downY, boolean isButton,
-            boolean gestureSwipeLeft) = 15;
-
-    /**
      * Sent when some system ui state changes.
      */
     void onSystemUiStateChanged(int stateFlags) = 16;
@@ -110,4 +98,14 @@
       * Sent when screen started turning off.
       */
      void onScreenTurningOff() = 24;
+
+     /**
+      * Sent when split keyboard shortcut is triggered to enter stage split.
+      */
+     void enterStageSplitFromRunningApp(boolean leftOrTop) = 25;
+
+     /**
+      * Sent when the surface for navigation bar is created or changed
+      */
+     void onNavigationBarSurface(in SurfaceControl surface) = 26;
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
index 2b2b05ce..1c532fe 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/ISystemUiProxy.aidl
@@ -24,7 +24,6 @@
 import android.view.MotionEvent;
 
 import com.android.systemui.shared.recents.model.Task;
-import com.android.systemui.shared.system.RemoteTransitionCompat;
 
 /**
  * Temporary callbacks into SystemUI.
@@ -106,9 +105,6 @@
     /** Sets home rotation enabled. */
     void setHomeRotationEnabled(boolean enabled) = 45;
 
-    /** Notifies that a swipe-up gesture has started */
-    oneway void notifySwipeUpGestureStarted() = 46;
-
     /** Notifies when taskbar status updated */
     oneway void notifyTaskbarStatus(boolean visible, boolean stashed) = 47;
 
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSamplingInstance.kt b/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt
similarity index 74%
rename from packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSamplingInstance.kt
rename to packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt
index cd4b999..0ee813b 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSamplingInstance.kt
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/regionsampling/RegionSampler.kt
@@ -24,15 +24,13 @@
 import java.io.PrintWriter
 import java.util.concurrent.Executor
 
-/**
- * Class for instance of RegionSamplingHelper
- */
-open class RegionSamplingInstance(
-        sampledView: View?,
-        mainExecutor: Executor?,
-        bgExecutor: Executor?,
-        regionSamplingEnabled: Boolean,
-        updateFun: UpdateColorCallback
+/** Class for instance of RegionSamplingHelper */
+open class RegionSampler(
+    sampledView: View?,
+    mainExecutor: Executor?,
+    bgExecutor: Executor?,
+    regionSamplingEnabled: Boolean,
+    updateFun: UpdateColorCallback
 ) {
     private var regionDarkness = RegionDarkness.DEFAULT
     private var samplingBounds = Rect()
@@ -40,23 +38,13 @@
     @VisibleForTesting var regionSampler: RegionSamplingHelper? = null
     private var lightForegroundColor = Color.WHITE
     private var darkForegroundColor = Color.BLACK
-    /**
-     * Interface for method to be passed into RegionSamplingHelper
-     */
-    @FunctionalInterface
-    interface UpdateColorCallback {
-        /**
-         * Method to update the foreground colors after clock darkness changed.
-         */
-        fun updateColors()
-    }
 
     @VisibleForTesting
     open fun createRegionSamplingHelper(
-            sampledView: View,
-            callback: SamplingCallback,
-            mainExecutor: Executor?,
-            bgExecutor: Executor?
+        sampledView: View,
+        callback: SamplingCallback,
+        mainExecutor: Executor?,
+        bgExecutor: Executor?
     ): RegionSamplingHelper {
         return RegionSamplingHelper(sampledView, callback, mainExecutor, bgExecutor)
     }
@@ -77,7 +65,7 @@
      *
      * @return the determined foreground color
      */
-    fun currentForegroundColor(): Int{
+    fun currentForegroundColor(): Int {
         return if (regionDarkness.isDark) {
             lightForegroundColor
         } else {
@@ -97,41 +85,37 @@
         return regionDarkness
     }
 
-    /**
-     * Start region sampler
-     */
+    /** Start region sampler */
     fun startRegionSampler() {
         regionSampler?.start(samplingBounds)
     }
 
-    /**
-     * Stop region sampler
-     */
+    /** Stop region sampler */
     fun stopRegionSampler() {
         regionSampler?.stop()
     }
 
-    /**
-     * Dump region sampler
-     */
+    /** Dump region sampler */
     fun dump(pw: PrintWriter) {
         regionSampler?.dump(pw)
     }
 
     init {
         if (regionSamplingEnabled && sampledView != null) {
-            regionSampler = createRegionSamplingHelper(sampledView,
+            regionSampler =
+                createRegionSamplingHelper(
+                    sampledView,
                     object : SamplingCallback {
                         override fun onRegionDarknessChanged(isRegionDark: Boolean) {
                             regionDarkness = convertToClockDarkness(isRegionDark)
-                            updateFun.updateColors()
+                            updateFun()
                         }
                         /**
-                        * The method getLocationOnScreen is used to obtain the view coordinates
-                        * relative to its left and top edges on the device screen.
-                        * Directly accessing the X and Y coordinates of the view returns the
-                        * location relative to its parent view instead.
-                        */
+                         * The method getLocationOnScreen is used to obtain the view coordinates
+                         * relative to its left and top edges on the device screen. Directly
+                         * accessing the X and Y coordinates of the view returns the location
+                         * relative to its parent view instead.
+                         */
                         override fun getSampledRegion(sampledView: View): Rect {
                             val screenLocation = tmpScreenLocation
                             sampledView.getLocationOnScreen(screenLocation)
@@ -147,8 +131,13 @@
                         override fun isSamplingEnabled(): Boolean {
                             return regionSamplingEnabled
                         }
-                    }, mainExecutor, bgExecutor)
+                    },
+                    mainExecutor,
+                    bgExecutor
+                )
         }
         regionSampler?.setWindowVisible(true)
     }
 }
+
+typealias UpdateColorCallback = () -> Unit
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
deleted file mode 100644
index 37e706a..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationAdapterCompat.java
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.shared.system;
-
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
-import static android.view.WindowManager.TRANSIT_CLOSE;
-import static android.view.WindowManager.TRANSIT_OLD_NONE;
-import static android.view.WindowManager.TRANSIT_OPEN;
-import static android.view.WindowManager.TRANSIT_TO_BACK;
-import static android.view.WindowManager.TRANSIT_TO_FRONT;
-import static android.view.WindowManager.TransitionOldType;
-import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
-
-import android.annotation.SuppressLint;
-import android.app.IApplicationThread;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.ArrayMap;
-import android.util.Log;
-import android.view.IRemoteAnimationFinishedCallback;
-import android.view.IRemoteAnimationRunner;
-import android.view.RemoteAnimationAdapter;
-import android.view.RemoteAnimationTarget;
-import android.view.SurfaceControl;
-import android.window.IRemoteTransition;
-import android.window.IRemoteTransitionFinishedCallback;
-import android.window.RemoteTransition;
-import android.window.TransitionInfo;
-
-import com.android.wm.shell.util.CounterRotator;
-
-/**
- * @see RemoteAnimationAdapter
- */
-public class RemoteAnimationAdapterCompat {
-
-    private final RemoteAnimationAdapter mWrapped;
-    private final RemoteTransitionCompat mRemoteTransition;
-
-    public RemoteAnimationAdapterCompat(RemoteAnimationRunnerCompat runner, long duration,
-            long statusBarTransitionDelay, IApplicationThread appThread) {
-        mWrapped = new RemoteAnimationAdapter(wrapRemoteAnimationRunner(runner), duration,
-                statusBarTransitionDelay);
-        mRemoteTransition = buildRemoteTransition(runner, appThread);
-    }
-
-    public RemoteAnimationAdapter getWrapped() {
-        return mWrapped;
-    }
-
-    /** Helper to just build a remote transition. Use this if the legacy adapter isn't needed. */
-    public static RemoteTransitionCompat buildRemoteTransition(RemoteAnimationRunnerCompat runner,
-            IApplicationThread appThread) {
-        return new RemoteTransitionCompat(
-                new RemoteTransition(wrapRemoteTransition(runner), appThread));
-    }
-
-    public RemoteTransitionCompat getRemoteTransition() {
-        return mRemoteTransition;
-    }
-
-    /** Wraps a RemoteAnimationRunnerCompat in an IRemoteAnimationRunner. */
-    public static IRemoteAnimationRunner.Stub wrapRemoteAnimationRunner(
-            final RemoteAnimationRunnerCompat remoteAnimationAdapter) {
-        return new IRemoteAnimationRunner.Stub() {
-            @Override
-            public void onAnimationStart(@TransitionOldType int transit,
-                    RemoteAnimationTarget[] apps,
-                    RemoteAnimationTarget[] wallpapers,
-                    RemoteAnimationTarget[] nonApps,
-                    final IRemoteAnimationFinishedCallback finishedCallback) {
-                final Runnable animationFinishedCallback = new Runnable() {
-                    @Override
-                    public void run() {
-                        try {
-                            finishedCallback.onAnimationFinished();
-                        } catch (RemoteException e) {
-                            Log.e("ActivityOptionsCompat", "Failed to call app controlled animation"
-                                    + " finished callback", e);
-                        }
-                    }
-                };
-                remoteAnimationAdapter.onAnimationStart(transit, apps, wallpapers,
-                        nonApps, animationFinishedCallback);
-            }
-
-            @Override
-            public void onAnimationCancelled(boolean isKeyguardOccluded) {
-                remoteAnimationAdapter.onAnimationCancelled();
-            }
-        };
-    }
-
-    private static IRemoteTransition.Stub wrapRemoteTransition(
-            final RemoteAnimationRunnerCompat remoteAnimationAdapter) {
-        return new IRemoteTransition.Stub() {
-            final ArrayMap<IBinder, Runnable> mFinishRunnables = new ArrayMap<>();
-
-            @Override
-            public void startAnimation(IBinder token, TransitionInfo info,
-                    SurfaceControl.Transaction t,
-                    IRemoteTransitionFinishedCallback finishCallback) {
-                final ArrayMap<SurfaceControl, SurfaceControl> leashMap = new ArrayMap<>();
-                final RemoteAnimationTarget[] apps =
-                        RemoteAnimationTargetCompat.wrapApps(info, t, leashMap);
-                final RemoteAnimationTarget[] wallpapers =
-                        RemoteAnimationTargetCompat.wrapNonApps(
-                                info, true /* wallpapers */, t, leashMap);
-                final RemoteAnimationTarget[] nonApps =
-                        RemoteAnimationTargetCompat.wrapNonApps(
-                                info, false /* wallpapers */, t, leashMap);
-
-                // TODO(b/177438007): Move this set-up logic into launcher's animation impl.
-                boolean isReturnToHome = false;
-                TransitionInfo.Change launcherTask = null;
-                TransitionInfo.Change wallpaper = null;
-                int launcherLayer = 0;
-                int rotateDelta = 0;
-                float displayW = 0;
-                float displayH = 0;
-                for (int i = info.getChanges().size() - 1; i >= 0; --i) {
-                    final TransitionInfo.Change change = info.getChanges().get(i);
-                    // skip changes that we didn't wrap
-                    if (!leashMap.containsKey(change.getLeash())) continue;
-                    if (change.getTaskInfo() != null
-                            && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME) {
-                        isReturnToHome = change.getMode() == TRANSIT_OPEN
-                                || change.getMode() == TRANSIT_TO_FRONT;
-                        launcherTask = change;
-                        launcherLayer = info.getChanges().size() - i;
-                    } else if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) {
-                        wallpaper = change;
-                    }
-                    if (change.getParent() == null && change.getEndRotation() >= 0
-                            && change.getEndRotation() != change.getStartRotation()) {
-                        rotateDelta = change.getEndRotation() - change.getStartRotation();
-                        displayW = change.getEndAbsBounds().width();
-                        displayH = change.getEndAbsBounds().height();
-                    }
-                }
-
-                // Prepare for rotation if there is one
-                final CounterRotator counterLauncher = new CounterRotator();
-                final CounterRotator counterWallpaper = new CounterRotator();
-                if (launcherTask != null && rotateDelta != 0 && launcherTask.getParent() != null) {
-                    counterLauncher.setup(t, info.getChange(launcherTask.getParent()).getLeash(),
-                            rotateDelta, displayW, displayH);
-                    if (counterLauncher.getSurface() != null) {
-                        t.setLayer(counterLauncher.getSurface(), launcherLayer);
-                    }
-                }
-
-                if (isReturnToHome) {
-                    if (counterLauncher.getSurface() != null) {
-                        t.setLayer(counterLauncher.getSurface(), info.getChanges().size() * 3);
-                    }
-                    // Need to "boost" the closing things since that's what launcher expects.
-                    for (int i = info.getChanges().size() - 1; i >= 0; --i) {
-                        final TransitionInfo.Change change = info.getChanges().get(i);
-                        final SurfaceControl leash = leashMap.get(change.getLeash());
-                        // skip changes that we didn't wrap
-                        if (leash == null) continue;
-                        final int mode = info.getChanges().get(i).getMode();
-                        // Only deal with independent layers
-                        if (!TransitionInfo.isIndependent(change, info)) continue;
-                        if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) {
-                            t.setLayer(leash, info.getChanges().size() * 3 - i);
-                            counterLauncher.addChild(t, leash);
-                        }
-                    }
-                    // Make wallpaper visible immediately since launcher apparently won't do this.
-                    for (int i = wallpapers.length - 1; i >= 0; --i) {
-                        t.show(wallpapers[i].leash);
-                        t.setAlpha(wallpapers[i].leash, 1.f);
-                    }
-                } else {
-                    if (launcherTask != null) {
-                        counterLauncher.addChild(t, leashMap.get(launcherTask.getLeash()));
-                    }
-                    if (wallpaper != null && rotateDelta != 0 && wallpaper.getParent() != null) {
-                        counterWallpaper.setup(t, info.getChange(wallpaper.getParent()).getLeash(),
-                                rotateDelta, displayW, displayH);
-                        if (counterWallpaper.getSurface() != null) {
-                            t.setLayer(counterWallpaper.getSurface(), -1);
-                            counterWallpaper.addChild(t, leashMap.get(wallpaper.getLeash()));
-                        }
-                    }
-                }
-                t.apply();
-
-                final Runnable animationFinishedCallback = new Runnable() {
-                    @Override
-                    @SuppressLint("NewApi")
-                    public void run() {
-                        final SurfaceControl.Transaction finishTransaction =
-                                new SurfaceControl.Transaction();
-                        counterLauncher.cleanUp(finishTransaction);
-                        counterWallpaper.cleanUp(finishTransaction);
-                        // Release surface references now. This is apparently to free GPU memory
-                        // while doing quick operations (eg. during CTS).
-                        for (int i = info.getChanges().size() - 1; i >= 0; --i) {
-                            info.getChanges().get(i).getLeash().release();
-                        }
-                        // Don't release here since launcher might still be using them. Instead
-                        // let launcher release them (eg. via RemoteAnimationTargets)
-                        leashMap.clear();
-                        try {
-                            finishCallback.onTransitionFinished(null /* wct */, finishTransaction);
-                        } catch (RemoteException e) {
-                            Log.e("ActivityOptionsCompat", "Failed to call app controlled animation"
-                                    + " finished callback", e);
-                        }
-                    }
-                };
-                synchronized (mFinishRunnables) {
-                    mFinishRunnables.put(token, animationFinishedCallback);
-                }
-                // TODO(bc-unlcok): Pass correct transit type.
-                remoteAnimationAdapter.onAnimationStart(TRANSIT_OLD_NONE,
-                        apps, wallpapers, nonApps, () -> {
-                            synchronized (mFinishRunnables) {
-                                if (mFinishRunnables.remove(token) == null) return;
-                            }
-                            animationFinishedCallback.run();
-                        });
-            }
-
-            @Override
-            public void mergeAnimation(IBinder token, TransitionInfo info,
-                    SurfaceControl.Transaction t, IBinder mergeTarget,
-                    IRemoteTransitionFinishedCallback finishCallback) {
-                // TODO: hook up merge to recents onTaskAppeared if applicable. Until then, adapt
-                //       to legacy cancel.
-                final Runnable finishRunnable;
-                synchronized (mFinishRunnables) {
-                    finishRunnable = mFinishRunnables.remove(mergeTarget);
-                }
-                if (finishRunnable == null) return;
-                remoteAnimationAdapter.onAnimationCancelled();
-                finishRunnable.run();
-            }
-        };
-    }
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationDefinitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationDefinitionCompat.java
deleted file mode 100644
index ab55037..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationDefinitionCompat.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.shared.system;
-
-import android.view.RemoteAnimationDefinition;
-
-/**
- * @see RemoteAnimationDefinition
- */
-public class RemoteAnimationDefinitionCompat {
-
-    private final RemoteAnimationDefinition mWrapped = new RemoteAnimationDefinition();
-
-    public void addRemoteAnimation(int transition, RemoteAnimationAdapterCompat adapter) {
-        mWrapped.addRemoteAnimation(transition, adapter.getWrapped());
-    }
-
-    public void addRemoteAnimation(int transition, int activityTypeFilter,
-            RemoteAnimationAdapterCompat adapter) {
-        mWrapped.addRemoteAnimation(transition, activityTypeFilter, adapter.getWrapped());
-    }
-
-    public RemoteAnimationDefinition getWrapped() {
-        return mWrapped;
-    }
-}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
index 5809c81..93c8073 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteAnimationRunnerCompat.java
@@ -16,12 +16,197 @@
 
 package com.android.systemui.shared.system;
 
-import android.view.RemoteAnimationTarget;
-import android.view.WindowManager;
+import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
+import static android.view.WindowManager.TRANSIT_CLOSE;
+import static android.view.WindowManager.TRANSIT_OLD_NONE;
+import static android.view.WindowManager.TRANSIT_OPEN;
+import static android.view.WindowManager.TRANSIT_TO_BACK;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
+import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
 
-public interface RemoteAnimationRunnerCompat {
-    void onAnimationStart(@WindowManager.TransitionOldType int transit,
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Log;
+import android.view.IRemoteAnimationFinishedCallback;
+import android.view.IRemoteAnimationRunner;
+import android.view.RemoteAnimationTarget;
+import android.view.SurfaceControl;
+import android.view.WindowManager;
+import android.view.WindowManager.TransitionOldType;
+import android.window.IRemoteTransition;
+import android.window.IRemoteTransitionFinishedCallback;
+import android.window.TransitionInfo;
+
+import com.android.wm.shell.util.CounterRotator;
+
+public abstract class RemoteAnimationRunnerCompat extends IRemoteAnimationRunner.Stub {
+
+    public abstract void onAnimationStart(@WindowManager.TransitionOldType int transit,
             RemoteAnimationTarget[] apps, RemoteAnimationTarget[] wallpapers,
             RemoteAnimationTarget[] nonApps, Runnable finishedCallback);
-    void onAnimationCancelled();
+
+    @Override
+    public final void onAnimationStart(@TransitionOldType int transit,
+            RemoteAnimationTarget[] apps,
+            RemoteAnimationTarget[] wallpapers,
+            RemoteAnimationTarget[] nonApps,
+            final IRemoteAnimationFinishedCallback finishedCallback) {
+
+        onAnimationStart(transit, apps, wallpapers,
+                nonApps, () -> {
+                    try {
+                        finishedCallback.onAnimationFinished();
+                    } catch (RemoteException e) {
+                        Log.e("ActivityOptionsCompat", "Failed to call app controlled animation"
+                                + " finished callback", e);
+                    }
+                });
+    }
+
+    public IRemoteTransition toRemoteTransition() {
+        return new IRemoteTransition.Stub() {
+            final ArrayMap<IBinder, Runnable> mFinishRunnables = new ArrayMap<>();
+
+            @Override
+            public void startAnimation(IBinder token, TransitionInfo info,
+                    SurfaceControl.Transaction t,
+                    IRemoteTransitionFinishedCallback finishCallback) {
+                final ArrayMap<SurfaceControl, SurfaceControl> leashMap = new ArrayMap<>();
+                final RemoteAnimationTarget[] apps =
+                        RemoteAnimationTargetCompat.wrapApps(info, t, leashMap);
+                final RemoteAnimationTarget[] wallpapers =
+                        RemoteAnimationTargetCompat.wrapNonApps(
+                                info, true /* wallpapers */, t, leashMap);
+                final RemoteAnimationTarget[] nonApps =
+                        RemoteAnimationTargetCompat.wrapNonApps(
+                                info, false /* wallpapers */, t, leashMap);
+
+                // TODO(b/177438007): Move this set-up logic into launcher's animation impl.
+                boolean isReturnToHome = false;
+                TransitionInfo.Change launcherTask = null;
+                TransitionInfo.Change wallpaper = null;
+                int launcherLayer = 0;
+                int rotateDelta = 0;
+                float displayW = 0;
+                float displayH = 0;
+                for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+                    final TransitionInfo.Change change = info.getChanges().get(i);
+                    // skip changes that we didn't wrap
+                    if (!leashMap.containsKey(change.getLeash())) continue;
+                    if (change.getTaskInfo() != null
+                            && change.getTaskInfo().getActivityType() == ACTIVITY_TYPE_HOME) {
+                        isReturnToHome = change.getMode() == TRANSIT_OPEN
+                                || change.getMode() == TRANSIT_TO_FRONT;
+                        launcherTask = change;
+                        launcherLayer = info.getChanges().size() - i;
+                    } else if ((change.getFlags() & FLAG_IS_WALLPAPER) != 0) {
+                        wallpaper = change;
+                    }
+                    if (change.getParent() == null && change.getEndRotation() >= 0
+                            && change.getEndRotation() != change.getStartRotation()) {
+                        rotateDelta = change.getEndRotation() - change.getStartRotation();
+                        displayW = change.getEndAbsBounds().width();
+                        displayH = change.getEndAbsBounds().height();
+                    }
+                }
+
+                // Prepare for rotation if there is one
+                final CounterRotator counterLauncher = new CounterRotator();
+                final CounterRotator counterWallpaper = new CounterRotator();
+                if (launcherTask != null && rotateDelta != 0 && launcherTask.getParent() != null) {
+                    counterLauncher.setup(t, info.getChange(launcherTask.getParent()).getLeash(),
+                            rotateDelta, displayW, displayH);
+                    if (counterLauncher.getSurface() != null) {
+                        t.setLayer(counterLauncher.getSurface(), launcherLayer);
+                    }
+                }
+
+                if (isReturnToHome) {
+                    if (counterLauncher.getSurface() != null) {
+                        t.setLayer(counterLauncher.getSurface(), info.getChanges().size() * 3);
+                    }
+                    // Need to "boost" the closing things since that's what launcher expects.
+                    for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+                        final TransitionInfo.Change change = info.getChanges().get(i);
+                        final SurfaceControl leash = leashMap.get(change.getLeash());
+                        // skip changes that we didn't wrap
+                        if (leash == null) continue;
+                        final int mode = info.getChanges().get(i).getMode();
+                        // Only deal with independent layers
+                        if (!TransitionInfo.isIndependent(change, info)) continue;
+                        if (mode == TRANSIT_CLOSE || mode == TRANSIT_TO_BACK) {
+                            t.setLayer(leash, info.getChanges().size() * 3 - i);
+                            counterLauncher.addChild(t, leash);
+                        }
+                    }
+                    // Make wallpaper visible immediately since launcher apparently won't do this.
+                    for (int i = wallpapers.length - 1; i >= 0; --i) {
+                        t.show(wallpapers[i].leash);
+                        t.setAlpha(wallpapers[i].leash, 1.f);
+                    }
+                } else {
+                    if (launcherTask != null) {
+                        counterLauncher.addChild(t, leashMap.get(launcherTask.getLeash()));
+                    }
+                    if (wallpaper != null && rotateDelta != 0 && wallpaper.getParent() != null) {
+                        counterWallpaper.setup(t, info.getChange(wallpaper.getParent()).getLeash(),
+                                rotateDelta, displayW, displayH);
+                        if (counterWallpaper.getSurface() != null) {
+                            t.setLayer(counterWallpaper.getSurface(), -1);
+                            counterWallpaper.addChild(t, leashMap.get(wallpaper.getLeash()));
+                        }
+                    }
+                }
+                t.apply();
+
+                final Runnable animationFinishedCallback = () -> {
+                    final SurfaceControl.Transaction finishTransaction =
+                            new SurfaceControl.Transaction();
+                    counterLauncher.cleanUp(finishTransaction);
+                    counterWallpaper.cleanUp(finishTransaction);
+                    // Release surface references now. This is apparently to free GPU memory
+                    // while doing quick operations (eg. during CTS).
+                    for (int i = info.getChanges().size() - 1; i >= 0; --i) {
+                        info.getChanges().get(i).getLeash().release();
+                    }
+                    // Don't release here since launcher might still be using them. Instead
+                    // let launcher release them (eg. via RemoteAnimationTargets)
+                    leashMap.clear();
+                    try {
+                        finishCallback.onTransitionFinished(null /* wct */, finishTransaction);
+                    } catch (RemoteException e) {
+                        Log.e("ActivityOptionsCompat", "Failed to call app controlled animation"
+                                + " finished callback", e);
+                    }
+                };
+                synchronized (mFinishRunnables) {
+                    mFinishRunnables.put(token, animationFinishedCallback);
+                }
+                // TODO(bc-unlcok): Pass correct transit type.
+                onAnimationStart(TRANSIT_OLD_NONE,
+                        apps, wallpapers, nonApps, () -> {
+                            synchronized (mFinishRunnables) {
+                                if (mFinishRunnables.remove(token) == null) return;
+                            }
+                            animationFinishedCallback.run();
+                        });
+            }
+
+            @Override
+            public void mergeAnimation(IBinder token, TransitionInfo info,
+                    SurfaceControl.Transaction t, IBinder mergeTarget,
+                    IRemoteTransitionFinishedCallback finishCallback) throws RemoteException {
+                // TODO: hook up merge to recents onTaskAppeared if applicable. Until then, adapt
+                //       to legacy cancel.
+                final Runnable finishRunnable;
+                synchronized (mFinishRunnables) {
+                    finishRunnable = mFinishRunnables.remove(mergeTarget);
+                }
+                if (finishRunnable == null) return;
+                onAnimationCancelled(false /* isKeyguardOccluded */);
+                finishRunnable.run();
+            }
+        };
+    }
 }
\ No newline at end of file
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
index d6655a7..d4d3d25 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
@@ -18,29 +18,22 @@
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
-import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.view.RemoteAnimationTarget.MODE_CLOSING;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_CLOSE;
-import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_LOCKED;
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
-import static android.window.TransitionFilter.CONTAINER_ORDER_TOP;
 
 import static com.android.systemui.shared.system.RemoteAnimationTargetCompat.newTarget;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
 import android.app.IApplicationThread;
-import android.content.ComponentName;
 import android.graphics.Rect;
 import android.os.IBinder;
-import android.os.Parcelable;
 import android.os.RemoteException;
 import android.util.ArrayMap;
 import android.util.Log;
@@ -53,72 +46,23 @@
 import android.window.PictureInPictureSurfaceTransaction;
 import android.window.RemoteTransition;
 import android.window.TaskSnapshot;
-import android.window.TransitionFilter;
 import android.window.TransitionInfo;
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.DataClass;
 import com.android.systemui.shared.recents.model.ThumbnailData;
 
 import java.util.ArrayList;
-import java.util.concurrent.Executor;
 
 /**
- * Wrapper to expose RemoteTransition (shell transitions) to Launcher.
- *
- * @see IRemoteTransition
- * @see TransitionFilter
+ * Helper class to build {@link RemoteTransition} objects
  */
-@DataClass
-public class RemoteTransitionCompat implements Parcelable {
+public class RemoteTransitionCompat {
     private static final String TAG = "RemoteTransitionCompat";
 
-    @NonNull final RemoteTransition mTransition;
-    @Nullable TransitionFilter mFilter = null;
-
-    RemoteTransitionCompat(RemoteTransition transition) {
-        mTransition = transition;
-    }
-
-    public RemoteTransitionCompat(@NonNull RemoteTransitionRunner runner,
-            @NonNull Executor executor, @Nullable IApplicationThread appThread) {
-        IRemoteTransition remote = new IRemoteTransition.Stub() {
-            @Override
-            public void startAnimation(IBinder transition, TransitionInfo info,
-                    SurfaceControl.Transaction t,
-                    IRemoteTransitionFinishedCallback finishedCallback) {
-                final Runnable finishAdapter = () ->  {
-                    try {
-                        finishedCallback.onTransitionFinished(null /* wct */, null /* sct */);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Failed to call transition finished callback", e);
-                    }
-                };
-                executor.execute(() -> runner.startAnimation(transition, info, t, finishAdapter));
-            }
-
-            @Override
-            public void mergeAnimation(IBinder transition, TransitionInfo info,
-                    SurfaceControl.Transaction t, IBinder mergeTarget,
-                    IRemoteTransitionFinishedCallback finishedCallback) {
-                final Runnable finishAdapter = () ->  {
-                    try {
-                        finishedCallback.onTransitionFinished(null /* wct */, null /* sct */);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Failed to call transition finished callback", e);
-                    }
-                };
-                executor.execute(() -> runner.mergeAnimation(transition, info, t, mergeTarget,
-                        finishAdapter));
-            }
-        };
-        mTransition = new RemoteTransition(remote, appThread);
-    }
-
     /** Constructor specifically for recents animation */
-    public RemoteTransitionCompat(RecentsAnimationListener recents,
+    public static RemoteTransition newRemoteTransition(RecentsAnimationListener recents,
             RecentsAnimationControllerCompat controller, IApplicationThread appThread) {
         IRemoteTransition remote = new IRemoteTransition.Stub() {
             final RecentsControllerWrap mRecentsSession = new RecentsControllerWrap();
@@ -193,25 +137,7 @@
                 mRecentsSession.commitTasksAppearedIfNeeded(recents);
             }
         };
-        mTransition = new RemoteTransition(remote, appThread);
-    }
-
-    /** Adds a filter check that restricts this remote transition to home open transitions. */
-    public void addHomeOpenCheck(ComponentName homeActivity) {
-        if (mFilter == null) {
-            mFilter = new TransitionFilter();
-        }
-        // No need to handle the transition that also dismisses keyguard.
-        mFilter.mNotFlags = TRANSIT_FLAG_KEYGUARD_GOING_AWAY;
-        mFilter.mRequirements =
-                new TransitionFilter.Requirement[]{new TransitionFilter.Requirement(),
-                        new TransitionFilter.Requirement()};
-        mFilter.mRequirements[0].mActivityType = ACTIVITY_TYPE_HOME;
-        mFilter.mRequirements[0].mTopActivity = homeActivity;
-        mFilter.mRequirements[0].mModes = new int[]{TRANSIT_OPEN, TRANSIT_TO_FRONT};
-        mFilter.mRequirements[0].mOrder = CONTAINER_ORDER_TOP;
-        mFilter.mRequirements[1].mActivityType = ACTIVITY_TYPE_STANDARD;
-        mFilter.mRequirements[1].mModes = new int[]{TRANSIT_CLOSE, TRANSIT_TO_BACK};
+        return new RemoteTransition(remote, appThread);
     }
 
     /**
@@ -505,161 +431,4 @@
         @Override public void animateNavigationBarToApp(long duration) {
         }
     }
-
-
-
-    // Code below generated by codegen v1.0.23.
-    //
-    // DO NOT MODIFY!
-    // CHECKSTYLE:OFF Generated code
-    //
-    // To regenerate run:
-    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java
-    //
-    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
-    //   Settings > Editor > Code Style > Formatter Control
-    //@formatter:off
-
-
-    @DataClass.Generated.Member
-    /* package-private */ RemoteTransitionCompat(
-            @NonNull RemoteTransition transition,
-            @Nullable TransitionFilter filter) {
-        this.mTransition = transition;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mTransition);
-        this.mFilter = filter;
-
-        // onConstructed(); // You can define this method to get a callback
-    }
-
-    @DataClass.Generated.Member
-    public @NonNull RemoteTransition getTransition() {
-        return mTransition;
-    }
-
-    @DataClass.Generated.Member
-    public @Nullable TransitionFilter getFilter() {
-        return mFilter;
-    }
-
-    @Override
-    @DataClass.Generated.Member
-    public void writeToParcel(@NonNull android.os.Parcel dest, int flags) {
-        // You can override field parcelling by defining methods like:
-        // void parcelFieldName(Parcel dest, int flags) { ... }
-
-        byte flg = 0;
-        if (mFilter != null) flg |= 0x2;
-        dest.writeByte(flg);
-        dest.writeTypedObject(mTransition, flags);
-        if (mFilter != null) dest.writeTypedObject(mFilter, flags);
-    }
-
-    @Override
-    @DataClass.Generated.Member
-    public int describeContents() { return 0; }
-
-    /** @hide */
-    @SuppressWarnings({"unchecked", "RedundantCast"})
-    @DataClass.Generated.Member
-    protected RemoteTransitionCompat(@NonNull android.os.Parcel in) {
-        // You can override field unparcelling by defining methods like:
-        // static FieldType unparcelFieldName(Parcel in) { ... }
-
-        byte flg = in.readByte();
-        RemoteTransition transition = (RemoteTransition) in.readTypedObject(RemoteTransition.CREATOR);
-        TransitionFilter filter = (flg & 0x2) == 0 ? null : (TransitionFilter) in.readTypedObject(TransitionFilter.CREATOR);
-
-        this.mTransition = transition;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mTransition);
-        this.mFilter = filter;
-
-        // onConstructed(); // You can define this method to get a callback
-    }
-
-    @DataClass.Generated.Member
-    public static final @NonNull Parcelable.Creator<RemoteTransitionCompat> CREATOR
-            = new Parcelable.Creator<RemoteTransitionCompat>() {
-        @Override
-        public RemoteTransitionCompat[] newArray(int size) {
-            return new RemoteTransitionCompat[size];
-        }
-
-        @Override
-        public RemoteTransitionCompat createFromParcel(@NonNull android.os.Parcel in) {
-            return new RemoteTransitionCompat(in);
-        }
-    };
-
-    /**
-     * A builder for {@link RemoteTransitionCompat}
-     */
-    @SuppressWarnings("WeakerAccess")
-    @DataClass.Generated.Member
-    public static class Builder {
-
-        private @NonNull RemoteTransition mTransition;
-        private @Nullable TransitionFilter mFilter;
-
-        private long mBuilderFieldsSet = 0L;
-
-        public Builder(
-                @NonNull RemoteTransition transition) {
-            mTransition = transition;
-            com.android.internal.util.AnnotationValidations.validate(
-                    NonNull.class, null, mTransition);
-        }
-
-        @DataClass.Generated.Member
-        public @NonNull Builder setTransition(@NonNull RemoteTransition value) {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x1;
-            mTransition = value;
-            return this;
-        }
-
-        @DataClass.Generated.Member
-        public @NonNull Builder setFilter(@NonNull TransitionFilter value) {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x2;
-            mFilter = value;
-            return this;
-        }
-
-        /** Builds the instance. This builder should not be touched after calling this! */
-        public @NonNull RemoteTransitionCompat build() {
-            checkNotUsed();
-            mBuilderFieldsSet |= 0x4; // Mark builder used
-
-            if ((mBuilderFieldsSet & 0x2) == 0) {
-                mFilter = null;
-            }
-            RemoteTransitionCompat o = new RemoteTransitionCompat(
-                    mTransition,
-                    mFilter);
-            return o;
-        }
-
-        private void checkNotUsed() {
-            if ((mBuilderFieldsSet & 0x4) != 0) {
-                throw new IllegalStateException(
-                        "This Builder should not be reused. Use a new Builder instance instead");
-            }
-        }
-    }
-
-    @DataClass.Generated(
-            time = 1629321609807L,
-            codegenVersion = "1.0.23",
-            sourceFile = "frameworks/base/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.java",
-            inputSignatures = "private static final  java.lang.String TAG\nfinal @android.annotation.NonNull android.window.RemoteTransition mTransition\n @android.annotation.Nullable android.window.TransitionFilter mFilter\npublic  void addHomeOpenCheck(android.content.ComponentName)\nclass RemoteTransitionCompat extends java.lang.Object implements [android.os.Parcelable]\nprivate  com.android.systemui.shared.system.RecentsAnimationControllerCompat mWrapped\nprivate  android.window.IRemoteTransitionFinishedCallback mFinishCB\nprivate  android.window.WindowContainerToken mPausingTask\nprivate  android.window.WindowContainerToken mPipTask\nprivate  android.window.TransitionInfo mInfo\nprivate  android.view.SurfaceControl mOpeningLeash\nprivate  android.util.ArrayMap<android.view.SurfaceControl,android.view.SurfaceControl> mLeashMap\nprivate  android.window.PictureInPictureSurfaceTransaction mPipTransaction\nprivate  android.os.IBinder mTransition\n  void setup(com.android.systemui.shared.system.RecentsAnimationControllerCompat,android.window.TransitionInfo,android.window.IRemoteTransitionFinishedCallback,android.window.WindowContainerToken,android.window.WindowContainerToken,android.util.ArrayMap<android.view.SurfaceControl,android.view.SurfaceControl>,android.os.IBinder)\n @android.annotation.SuppressLint boolean merge(android.window.TransitionInfo,android.view.SurfaceControl.Transaction,com.android.systemui.shared.system.RecentsAnimationListener)\npublic @java.lang.Override com.android.systemui.shared.recents.model.ThumbnailData screenshotTask(int)\npublic @java.lang.Override void setInputConsumerEnabled(boolean)\npublic @java.lang.Override void setAnimationTargetsBehindSystemBars(boolean)\npublic @java.lang.Override void hideCurrentInputMethod()\npublic @java.lang.Override void setFinishTaskTransaction(int,android.window.PictureInPictureSurfaceTransaction,android.view.SurfaceControl)\npublic @java.lang.Override @android.annotation.SuppressLint void finish(boolean,boolean)\npublic @java.lang.Override void setDeferCancelUntilNextTransition(boolean,boolean)\npublic @java.lang.Override void cleanupScreenshot()\npublic @java.lang.Override void setWillFinishToHome(boolean)\npublic @java.lang.Override boolean removeTask(int)\npublic @java.lang.Override void detachNavigationBarFromApp(boolean)\npublic @java.lang.Override void animateNavigationBarToApp(long)\nclass RecentsControllerWrap extends com.android.systemui.shared.system.RecentsAnimationControllerCompat implements []\n@com.android.internal.util.DataClass")
-    @Deprecated
-    private void __metadata() {}
-
-
-    //@formatter:on
-    // End of generated code
-
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionRunner.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionRunner.java
deleted file mode 100644
index accc456..0000000
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionRunner.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.shared.system;
-
-import android.os.IBinder;
-import android.view.SurfaceControl;
-import android.window.TransitionInfo;
-
-/** Interface for something that runs a remote transition animation. */
-public interface RemoteTransitionRunner {
-    /**
-     * Starts a transition animation. Once complete, the implementation should call
-     * `finishCallback`.
-     */
-    void startAnimation(IBinder transition, TransitionInfo info, SurfaceControl.Transaction t,
-            Runnable finishCallback);
-
-    /**
-     * Attempts to merge a transition into the currently-running animation. If merge is not
-     * possible/supported, this should do nothing. Otherwise, the implementation should call
-     * `finishCallback` immediately to indicate that it merged the transition.
-     *
-     * @param transition The transition that wants to be merged into the running animation.
-     * @param mergeTarget The transition to merge into (that this runner is currently animating).
-     */
-    default void mergeAnimation(IBinder transition, TransitionInfo info,
-            SurfaceControl.Transaction t, IBinder mergeTarget, Runnable finishCallback) { }
-}
diff --git a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
index bb3df8f..7b216017 100644
--- a/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
+++ b/packages/SystemUI/src-debug/com/android/systemui/flags/FlagsModule.kt
@@ -18,17 +18,15 @@
 
 import android.content.Context
 import android.os.Handler
-import com.android.internal.statusbar.IStatusBarService
 import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.flags.FeatureFlagsDebug.ALL_FLAGS
 import com.android.systemui.util.settings.SettingsUtilModule
 import dagger.Binds
 import dagger.Module
 import dagger.Provides
-import javax.inject.Named
 
 @Module(includes = [
     FeatureFlagsDebugStartableModule::class,
+    FlagsCommonModule::class,
     ServerFlagReaderModule::class,
     SettingsUtilModule::class,
 ])
@@ -43,20 +41,5 @@
         fun provideFlagManager(context: Context, @Main handler: Handler): FlagManager {
             return FlagManager(context, handler)
         }
-
-        @JvmStatic
-        @Provides
-        @Named(ALL_FLAGS)
-        fun providesAllFlags(): Map<Int, Flag<*>> = Flags.collectFlags()
-
-        @JvmStatic
-        @Provides
-        fun providesRestarter(barService: IStatusBarService): Restarter {
-            return object: Restarter {
-                override fun restart() {
-                    barService.restart()
-                }
-            }
-        }
     }
 }
diff --git a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
index 0f7e732..aef8876 100644
--- a/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
+++ b/packages/SystemUI/src-release/com/android/systemui/flags/FlagsModule.kt
@@ -16,29 +16,15 @@
 
 package com.android.systemui.flags
 
-import com.android.internal.statusbar.IStatusBarService
 import dagger.Binds
 import dagger.Module
-import dagger.Provides
 
 @Module(includes = [
     FeatureFlagsReleaseStartableModule::class,
+    FlagsCommonModule::class,
     ServerFlagReaderModule::class
 ])
 abstract class FlagsModule {
     @Binds
     abstract fun bindsFeatureFlagRelease(impl: FeatureFlagsRelease): FeatureFlags
-
-    @Module
-    companion object {
-        @JvmStatic
-        @Provides
-        fun providesRestarter(barService: IStatusBarService): Restarter {
-            return object: Restarter {
-                override fun restart() {
-                    barService.restart()
-                }
-            }
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 40a96b0..c9b8712 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -34,25 +34,27 @@
 import com.android.systemui.flags.Flags.REGION_SAMPLING
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.log.dagger.KeyguardClockLog
 import com.android.systemui.plugins.ClockController
 import com.android.systemui.plugins.log.LogBuffer
-import com.android.systemui.shared.regionsampling.RegionSamplingInstance
+import com.android.systemui.shared.regionsampling.RegionSampler
 import com.android.systemui.statusbar.policy.BatteryController
 import com.android.systemui.statusbar.policy.BatteryController.BatteryStateChangeCallback
 import com.android.systemui.statusbar.policy.ConfigurationController
-import java.io.PrintWriter
-import java.util.Locale
-import java.util.TimeZone
-import java.util.concurrent.Executor
-import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.DisposableHandle
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.launch
+import java.io.PrintWriter
+import java.util.Locale
+import java.util.TimeZone
+import java.util.concurrent.Executor
+import javax.inject.Inject
 
 /**
  * Controller for a Clock provided by the registry and used on the keyguard. Instantiated by
@@ -69,14 +71,17 @@
     private val context: Context,
     @Main private val mainExecutor: Executor,
     @Background private val bgExecutor: Executor,
-    @KeyguardClockLog private val logBuffer: LogBuffer,
+    @KeyguardClockLog private val logBuffer: LogBuffer?,
     private val featureFlags: FeatureFlags
 ) {
     var clock: ClockController? = null
         set(value) {
             field = value
             if (value != null) {
-                value.setLogBuffer(logBuffer)
+                if (logBuffer != null) {
+                    value.setLogBuffer(logBuffer)
+                }
+
                 value.initialize(resources, dozeAmount, 0f)
                 updateRegionSamplers(value)
             }
@@ -139,21 +144,17 @@
             bgExecutor: Executor?,
             regionSamplingEnabled: Boolean,
             updateColors: () -> Unit
-    ): RegionSamplingInstance {
-        return RegionSamplingInstance(
+    ): RegionSampler {
+        return RegionSampler(
             sampledView,
             mainExecutor,
             bgExecutor,
             regionSamplingEnabled,
-            object : RegionSamplingInstance.UpdateColorCallback {
-                override fun updateColors() {
-                    updateColors()
-                }
-            })
+            updateFun = { updateColors() } )
     }
 
-    var smallRegionSampler: RegionSamplingInstance? = null
-    var largeRegionSampler: RegionSamplingInstance? = null
+    var smallRegionSampler: RegionSampler? = null
+    var largeRegionSampler: RegionSampler? = null
 
     private var smallClockIsDark = true
     private var largeClockIsDark = true
@@ -161,6 +162,7 @@
     private val configListener = object : ConfigurationController.ConfigurationListener {
         override fun onThemeChanged() {
             clock?.events?.onColorPaletteChanged(resources)
+            updateColors()
         }
 
         override fun onDensityOrFontScaleChanged() {
@@ -186,8 +188,10 @@
     private val keyguardUpdateMonitorCallback = object : KeyguardUpdateMonitorCallback() {
         override fun onKeyguardVisibilityChanged(visible: Boolean) {
             isKeyguardVisible = visible
-            if (!isKeyguardVisible) {
-                clock?.animations?.doze(if (isDozing) 1f else 0f)
+            if (!featureFlags.isEnabled(DOZING_MIGRATION_1)) {
+                if (!isKeyguardVisible) {
+                    clock?.animations?.doze(if (isDozing) 1f else 0f)
+                }
             }
         }
 
@@ -224,6 +228,7 @@
                 listenForDozing(this)
                 if (featureFlags.isEnabled(DOZING_MIGRATION_1)) {
                     listenForDozeAmountTransition(this)
+                    listenForAnyStateToAodTransition(this)
                 } else {
                     listenForDozeAmount(this)
                 }
@@ -276,6 +281,22 @@
         }
     }
 
+    /**
+     * When keyguard is displayed again after being gone, the clock must be reset to full
+     * dozing.
+     */
+    @VisibleForTesting
+    internal fun listenForAnyStateToAodTransition(scope: CoroutineScope): Job {
+        return scope.launch {
+            keyguardTransitionInteractor.anyStateToAodTransition.filter {
+                it.transitionState == TransitionState.FINISHED
+            }.collect {
+                dozeAmount = 1f
+                clock?.animations?.doze(dozeAmount)
+            }
+        }
+    }
+
     @VisibleForTesting
     internal fun listenForDozing(scope: CoroutineScope): Job {
         return scope.launch {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
index 71470e8..a0206f1 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardListenModel.kt
@@ -35,6 +35,7 @@
     val keyguardOccluded: Boolean,
     val occludingAppRequestingFp: Boolean,
     val primaryUser: Boolean,
+    val shouldListenSfpsState: Boolean,
     val shouldListenForFingerprintAssistant: Boolean,
     val switchingUser: Boolean,
     val udfps: Boolean,
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
index 93ee151..2bb3a5f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainer.java
@@ -89,6 +89,7 @@
 import com.android.systemui.Gefingerpoken;
 import com.android.systemui.R;
 import com.android.systemui.animation.Interpolators;
+import com.android.systemui.classifier.FalsingA11yDelegate;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.shared.system.SysUiStatsLog;
 import com.android.systemui.statusbar.policy.BaseUserSwitcherAdapter;
@@ -105,8 +106,9 @@
     static final int USER_TYPE_WORK_PROFILE = 2;
     static final int USER_TYPE_SECONDARY_USER = 3;
 
-    @IntDef({MODE_DEFAULT, MODE_ONE_HANDED, MODE_USER_SWITCHER})
+    @IntDef({MODE_UNINITIALIZED, MODE_DEFAULT, MODE_ONE_HANDED, MODE_USER_SWITCHER})
     public @interface Mode {}
+    static final int MODE_UNINITIALIZED = -1;
     static final int MODE_DEFAULT = 0;
     static final int MODE_ONE_HANDED = 1;
     static final int MODE_USER_SWITCHER = 2;
@@ -136,6 +138,7 @@
     private GlobalSettings mGlobalSettings;
     private FalsingManager mFalsingManager;
     private UserSwitcherController mUserSwitcherController;
+    private FalsingA11yDelegate mFalsingA11yDelegate;
     private AlertDialog mAlertDialog;
     private boolean mSwipeUpToRetry;
 
@@ -152,7 +155,11 @@
     private boolean mDisappearAnimRunning;
     private SwipeListener mSwipeListener;
     private ViewMode mViewMode = new DefaultViewMode();
-    private @Mode int mCurrentMode = MODE_DEFAULT;
+    /*
+     * Using MODE_UNINITIALIZED to mean the view mode is set to DefaultViewMode, but init() has not
+     * yet been called on it. This will happen when the ViewController is initialized.
+     */
+    private @Mode int mCurrentMode = MODE_UNINITIALIZED;
     private int mWidth = -1;
 
     private final WindowInsetsAnimation.Callback mWindowInsetsAnimationCallback =
@@ -318,7 +325,8 @@
 
     void initMode(@Mode int mode, GlobalSettings globalSettings, FalsingManager falsingManager,
             UserSwitcherController userSwitcherController,
-            UserSwitcherViewMode.UserSwitcherCallback userSwitcherCallback) {
+            UserSwitcherViewMode.UserSwitcherCallback userSwitcherCallback,
+            FalsingA11yDelegate falsingA11yDelegate) {
         if (mCurrentMode == mode) return;
         Log.i(TAG, "Switching mode from " + modeToString(mCurrentMode) + " to "
                 + modeToString(mode));
@@ -337,12 +345,15 @@
         }
         mGlobalSettings = globalSettings;
         mFalsingManager = falsingManager;
+        mFalsingA11yDelegate = falsingA11yDelegate;
         mUserSwitcherController = userSwitcherController;
         setupViewMode();
     }
 
     private String modeToString(@Mode int mode) {
         switch (mode) {
+            case MODE_UNINITIALIZED:
+                return "Uninitialized";
             case MODE_DEFAULT:
                 return "Default";
             case MODE_ONE_HANDED:
@@ -361,7 +372,7 @@
         }
 
         mViewMode.init(this, mGlobalSettings, mSecurityViewFlipper, mFalsingManager,
-                mUserSwitcherController);
+                mUserSwitcherController, mFalsingA11yDelegate);
     }
 
     @Mode int getMode() {
@@ -723,7 +734,8 @@
         default void init(@NonNull ConstraintLayout v, @NonNull GlobalSettings globalSettings,
                 @NonNull KeyguardSecurityViewFlipper viewFlipper,
                 @NonNull FalsingManager falsingManager,
-                @NonNull UserSwitcherController userSwitcherController) {};
+                @NonNull UserSwitcherController userSwitcherController,
+                @NonNull FalsingA11yDelegate falsingA11yDelegate) {};
 
         /** Reinitialize the location */
         default void updateSecurityViewLocation() {};
@@ -828,7 +840,8 @@
         public void init(@NonNull ConstraintLayout v, @NonNull GlobalSettings globalSettings,
                 @NonNull KeyguardSecurityViewFlipper viewFlipper,
                 @NonNull FalsingManager falsingManager,
-                @NonNull UserSwitcherController userSwitcherController) {
+                @NonNull UserSwitcherController userSwitcherController,
+                @NonNull FalsingA11yDelegate falsingA11yDelegate) {
             mView = v;
             mViewFlipper = viewFlipper;
 
@@ -865,6 +878,7 @@
                 this::setupUserSwitcher;
 
         private UserSwitcherCallback mUserSwitcherCallback;
+        private FalsingA11yDelegate mFalsingA11yDelegate;
 
         UserSwitcherViewMode(UserSwitcherCallback userSwitcherCallback) {
             mUserSwitcherCallback = userSwitcherCallback;
@@ -874,13 +888,15 @@
         public void init(@NonNull ConstraintLayout v, @NonNull GlobalSettings globalSettings,
                 @NonNull KeyguardSecurityViewFlipper viewFlipper,
                 @NonNull FalsingManager falsingManager,
-                @NonNull UserSwitcherController userSwitcherController) {
+                @NonNull UserSwitcherController userSwitcherController,
+                @NonNull FalsingA11yDelegate falsingA11yDelegate) {
             init(v, viewFlipper, globalSettings, /* leftAlignedByDefault= */false);
             mView = v;
             mViewFlipper = viewFlipper;
             mFalsingManager = falsingManager;
             mUserSwitcherController = userSwitcherController;
             mResources = v.getContext().getResources();
+            mFalsingA11yDelegate = falsingA11yDelegate;
 
             if (mUserSwitcherViewGroup == null) {
                 LayoutInflater.from(v.getContext()).inflate(
@@ -978,6 +994,7 @@
             mUserSwitcher.setText(currentUserName);
 
             KeyguardUserSwitcherAnchor anchor = mView.findViewById(R.id.user_switcher_anchor);
+            anchor.setAccessibilityDelegate(mFalsingA11yDelegate);
 
             BaseUserSwitcherAdapter adapter = new BaseUserSwitcherAdapter(mUserSwitcherController) {
                 @Override
@@ -1048,7 +1065,7 @@
 
             anchor.setOnClickListener((v) -> {
                 if (mFalsingManager.isFalseTap(LOW_PENALTY)) return;
-                mPopup = new KeyguardUserSwitcherPopupMenu(v.getContext(), mFalsingManager);
+                mPopup = new KeyguardUserSwitcherPopupMenu(mView.getContext(), mFalsingManager);
                 mPopup.setAnchorView(anchor);
                 mPopup.setAdapter(adapter);
                 mPopup.setOnItemClickListener((parent, view, pos, id) -> {
@@ -1137,7 +1154,8 @@
         public void init(@NonNull ConstraintLayout v, @NonNull GlobalSettings globalSettings,
                 @NonNull KeyguardSecurityViewFlipper viewFlipper,
                 @NonNull FalsingManager falsingManager,
-                @NonNull UserSwitcherController userSwitcherController) {
+                @NonNull UserSwitcherController userSwitcherController,
+                @NonNull FalsingA11yDelegate falsingA11yDelegate) {
             init(v, viewFlipper, globalSettings, /* leftAlignedByDefault= */true);
             mView = v;
             mViewFlipper = viewFlipper;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 81305f9..fbb114c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -59,6 +59,7 @@
 import com.android.systemui.Gefingerpoken;
 import com.android.systemui.R;
 import com.android.systemui.biometrics.SidefpsController;
+import com.android.systemui.classifier.FalsingA11yDelegate;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
@@ -100,6 +101,7 @@
     private final FeatureFlags mFeatureFlags;
     private final SessionTracker mSessionTracker;
     private final Optional<SidefpsController> mSidefpsController;
+    private final FalsingA11yDelegate mFalsingA11yDelegate;
 
     private int mLastOrientation = Configuration.ORIENTATION_UNDEFINED;
 
@@ -223,7 +225,7 @@
         @Override
         public void onSwipeUp() {
             if (!mUpdateMonitor.isFaceDetectionRunning()) {
-                boolean didFaceAuthRun = mUpdateMonitor.requestFaceAuth(true,
+                boolean didFaceAuthRun = mUpdateMonitor.requestFaceAuth(
                         FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER);
                 mKeyguardSecurityCallback.userActivity();
                 if (didFaceAuthRun) {
@@ -288,7 +290,8 @@
             FeatureFlags featureFlags,
             GlobalSettings globalSettings,
             SessionTracker sessionTracker,
-            Optional<SidefpsController> sidefpsController) {
+            Optional<SidefpsController> sidefpsController,
+            FalsingA11yDelegate falsingA11yDelegate) {
         super(view);
         mLockPatternUtils = lockPatternUtils;
         mUpdateMonitor = keyguardUpdateMonitor;
@@ -309,11 +312,13 @@
         mGlobalSettings = globalSettings;
         mSessionTracker = sessionTracker;
         mSidefpsController = sidefpsController;
+        mFalsingA11yDelegate = falsingA11yDelegate;
     }
 
     @Override
     public void onInit() {
         mSecurityViewFlipperController.init();
+        configureMode();
     }
 
     @Override
@@ -349,10 +354,21 @@
         if (!mSidefpsController.isPresent()) {
             return;
         }
-        if (mBouncerVisible
-                && getResources().getBoolean(R.bool.config_show_sidefps_hint_on_bouncer)
-                && mUpdateMonitor.isFingerprintDetectionRunning()
-                && !mUpdateMonitor.userNeedsStrongAuth()) {
+        final boolean sfpsEnabled = getResources().getBoolean(
+                R.bool.config_show_sidefps_hint_on_bouncer);
+        final boolean fpsDetectionRunning = mUpdateMonitor.isFingerprintDetectionRunning();
+        final boolean needsStrongAuth = mUpdateMonitor.userNeedsStrongAuth();
+
+        boolean toShow = mBouncerVisible && sfpsEnabled && fpsDetectionRunning && !needsStrongAuth;
+
+        if (DEBUG) {
+            Log.d(TAG, "sideFpsToShow=" + toShow + ", "
+                    + "mBouncerVisible=" + mBouncerVisible + ", "
+                    + "configEnabled=" + sfpsEnabled + ", "
+                    + "fpsDetectionRunning=" + fpsDetectionRunning + ", "
+                    + "needsStrongAuth=" + needsStrongAuth);
+        }
+        if (toShow) {
             mSidefpsController.get().show();
         } else {
             mSidefpsController.get().hide();
@@ -625,7 +641,7 @@
 
         mView.initMode(mode, mGlobalSettings, mFalsingManager, mUserSwitcherController,
                 () -> showMessage(getContext().getString(R.string.keyguard_unlock_to_continue),
-                        null));
+                        null), mFalsingA11yDelegate);
     }
 
     public void reportFailedUnlockAttempt(int userId, int timeoutMs) {
@@ -730,6 +746,7 @@
         private final UserSwitcherController mUserSwitcherController;
         private final SessionTracker mSessionTracker;
         private final Optional<SidefpsController> mSidefpsController;
+        private final FalsingA11yDelegate mFalsingA11yDelegate;
 
         @Inject
         Factory(KeyguardSecurityContainer view,
@@ -749,7 +766,8 @@
                 FeatureFlags featureFlags,
                 GlobalSettings globalSettings,
                 SessionTracker sessionTracker,
-                Optional<SidefpsController> sidefpsController) {
+                Optional<SidefpsController> sidefpsController,
+                FalsingA11yDelegate falsingA11yDelegate) {
             mView = view;
             mAdminSecondaryLockScreenControllerFactory = adminSecondaryLockScreenControllerFactory;
             mLockPatternUtils = lockPatternUtils;
@@ -767,6 +785,7 @@
             mUserSwitcherController = userSwitcherController;
             mSessionTracker = sessionTracker;
             mSidefpsController = sidefpsController;
+            mFalsingA11yDelegate = falsingA11yDelegate;
         }
 
         public KeyguardSecurityContainerController create(
@@ -777,7 +796,7 @@
                     mKeyguardStateController, securityCallback, mSecurityViewFlipperController,
                     mConfigurationController, mFalsingCollector, mFalsingManager,
                     mUserSwitcherController, mFeatureFlags, mGlobalSettings, mSessionTracker,
-                    mSidefpsController);
+                    mSidefpsController, mFalsingA11yDelegate);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index cc1f2fe..9d2dcf0 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -150,6 +150,7 @@
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.telephony.TelephonyListenerManager;
 import com.android.systemui.util.Assert;
+import com.android.systemui.util.settings.SecureSettings;
 
 import com.google.android.collect.Lists;
 
@@ -321,17 +322,20 @@
     private final ArrayList<WeakReference<KeyguardUpdateMonitorCallback>>
             mCallbacks = Lists.newArrayList();
     private ContentObserver mDeviceProvisionedObserver;
+    private ContentObserver mSfpsRequireScreenOnToAuthPrefObserver;
     private final ContentObserver mTimeFormatChangeObserver;
 
     private boolean mSwitchingUser;
 
     private boolean mDeviceInteractive;
+    private boolean mSfpsRequireScreenOnToAuthPrefEnabled;
     private final SubscriptionManager mSubscriptionManager;
     private final TelephonyListenerManager mTelephonyListenerManager;
     private final TrustManager mTrustManager;
     private final UserManager mUserManager;
     private final DevicePolicyManager mDevicePolicyManager;
     private final BroadcastDispatcher mBroadcastDispatcher;
+    private final SecureSettings mSecureSettings;
     private final InteractionJankMonitor mInteractionJankMonitor;
     private final LatencyTracker mLatencyTracker;
     private final StatusBarStateController mStatusBarStateController;
@@ -380,6 +384,7 @@
     protected Handler getHandler() {
         return mHandler;
     }
+
     private final Handler mHandler;
 
     private final IBiometricEnabledOnKeyguardCallback mBiometricEnabledCallback =
@@ -704,6 +709,7 @@
 
     /**
      * Request to listen for face authentication when an app is occluding keyguard.
+     *
      * @param request if true and mKeyguardOccluded, request face auth listening, else default
      *                to normal behavior.
      *                See {@link KeyguardUpdateMonitor#shouldListenForFace()}
@@ -716,6 +722,7 @@
 
     /**
      * Request to listen for fingerprint when an app is occluding keyguard.
+     *
      * @param request if true and mKeyguardOccluded, request fingerprint listening, else default
      *                to normal behavior.
      *                See {@link KeyguardUpdateMonitor#shouldListenForFingerprint(boolean)}
@@ -1615,7 +1622,7 @@
                 @Override
                 public void onUdfpsPointerDown(int sensorId) {
                     mLogger.logUdfpsPointerDown(sensorId);
-                    requestFaceAuth(true, FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
+                    requestFaceAuth(FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
                 }
 
                 /**
@@ -1930,6 +1937,7 @@
             Context context,
             @Main Looper mainLooper,
             BroadcastDispatcher broadcastDispatcher,
+            SecureSettings secureSettings,
             DumpManager dumpManager,
             @Background Executor backgroundExecutor,
             @Main Executor mainExecutor,
@@ -1972,6 +1980,7 @@
         mStatusBarState = mStatusBarStateController.getState();
         mLockPatternUtils = lockPatternUtils;
         mAuthController = authController;
+        mSecureSettings = secureSettings;
         dumpManager.registerDumpable(getClass().getName(), this);
         mSensorPrivacyManager = sensorPrivacyManager;
         mActiveUnlockConfig = activeUnlockConfiguration;
@@ -2214,9 +2223,35 @@
                                 Settings.System.TIME_12_24)));
             }
         };
+
         mContext.getContentResolver().registerContentObserver(
                 Settings.System.getUriFor(Settings.System.TIME_12_24),
                 false, mTimeFormatChangeObserver, UserHandle.USER_ALL);
+
+        updateSfpsRequireScreenOnToAuthPref();
+        mSfpsRequireScreenOnToAuthPrefObserver = new ContentObserver(mHandler) {
+            @Override
+            public void onChange(boolean selfChange) {
+                updateSfpsRequireScreenOnToAuthPref();
+            }
+        };
+
+        mContext.getContentResolver().registerContentObserver(
+                mSecureSettings.getUriFor(
+                        Settings.Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED),
+                false,
+                mSfpsRequireScreenOnToAuthPrefObserver,
+                getCurrentUser());
+    }
+
+    protected void updateSfpsRequireScreenOnToAuthPref() {
+        final int defaultSfpsRequireScreenOnToAuthValue =
+                mContext.getResources().getBoolean(
+                        com.android.internal.R.bool.config_requireScreenOnToAuthEnabled) ? 1 : 0;
+        mSfpsRequireScreenOnToAuthPrefEnabled = mSecureSettings.getIntForUser(
+                Settings.Secure.SFPS_REQUIRE_SCREEN_ON_TO_AUTH_ENABLED,
+                defaultSfpsRequireScreenOnToAuthValue,
+                getCurrentUser()) != 0;
     }
 
     private void initializeSimState() {
@@ -2261,6 +2296,22 @@
     }
 
     /**
+     * @return true if there's at least one sfps enrollment for the current user.
+     */
+    public boolean isSfpsEnrolled() {
+        return mAuthController.isSfpsEnrolled(getCurrentUser());
+    }
+
+    /**
+     * @return true if sfps HW is supported on this device. Can return true even if the user has
+     * not enrolled sfps. This may be false if called before onAllAuthenticatorsRegistered.
+     */
+    public boolean isSfpsSupported() {
+        return mAuthController.getSfpsProps() != null
+                && !mAuthController.getSfpsProps().isEmpty();
+    }
+
+    /**
      * @return true if there's at least one face enrolled
      */
     public boolean isFaceEnrolled() {
@@ -2357,14 +2408,12 @@
     /**
      * Requests face authentication if we're on a state where it's allowed.
      * This will re-trigger auth in case it fails.
-     * @param userInitiatedRequest true if the user explicitly requested face auth
      * @param reason One of the reasons {@link FaceAuthApiRequestReason} on why this API is being
      * invoked.
      * @return current face auth detection state, true if it is running.
      */
-    public boolean requestFaceAuth(boolean userInitiatedRequest,
-            @FaceAuthApiRequestReason String reason) {
-        mLogger.logFaceAuthRequested(userInitiatedRequest, reason);
+    public boolean requestFaceAuth(@FaceAuthApiRequestReason String reason) {
+        mLogger.logFaceAuthRequested(reason);
         updateFaceListeningState(BIOMETRIC_ACTION_START, apiRequestReasonToUiEvent(reason));
         return isFaceDetectionRunning();
     }
@@ -2585,13 +2634,21 @@
                 !(mFingerprintLockedOut && mBouncerIsOrWillBeShowing && mCredentialAttempted);
 
         final boolean isEncryptedOrLockdownForUser = isEncryptedOrLockdown(user);
+
         final boolean shouldListenUdfpsState = !isUdfps
                 || (!userCanSkipBouncer
-                    && !isEncryptedOrLockdownForUser
-                    && userDoesNotHaveTrust);
+                && !isEncryptedOrLockdownForUser
+                && userDoesNotHaveTrust);
 
-        final boolean shouldListen = shouldListenKeyguardState && shouldListenUserState
-                && shouldListenBouncerState && shouldListenUdfpsState && !isFingerprintLockedOut();
+        boolean shouldListenSideFpsState = true;
+        if (isSfpsSupported() && isSfpsEnrolled()) {
+            shouldListenSideFpsState =
+                    mSfpsRequireScreenOnToAuthPrefEnabled ? isDeviceInteractive() : true;
+        }
+
+        boolean shouldListen = shouldListenKeyguardState && shouldListenUserState
+                && shouldListenBouncerState && shouldListenUdfpsState && !isFingerprintLockedOut()
+                && shouldListenSideFpsState;
 
         maybeLogListenerModelData(
                 new KeyguardFingerprintListenModel(
@@ -2613,6 +2670,7 @@
                     mKeyguardOccluded,
                     mOccludingAppRequestingFp,
                     mIsPrimaryUser,
+                    shouldListenSideFpsState,
                     shouldListenForFingerprintAssistant,
                     mSwitchingUser,
                     isUdfps,
@@ -3714,6 +3772,11 @@
             mContext.getContentResolver().unregisterContentObserver(mTimeFormatChangeObserver);
         }
 
+        if (mSfpsRequireScreenOnToAuthPrefObserver != null) {
+            mContext.getContentResolver().unregisterContentObserver(
+                    mSfpsRequireScreenOnToAuthPrefObserver);
+        }
+
         try {
             ActivityManager.getService().unregisterUserSwitchObserver(mUserSwitchObserver);
         } catch (RemoteException e) {
@@ -3786,6 +3849,13 @@
                 pw.println("        mBouncerIsOrWillBeShowing=" + mBouncerIsOrWillBeShowing);
                 pw.println("        mStatusBarState=" + StatusBarState.toString(mStatusBarState));
                 pw.println("        mUdfpsBouncerShowing=" + mUdfpsBouncerShowing);
+            } else if (isSfpsSupported()) {
+                pw.println("        sfpsEnrolled=" + isSfpsEnrolled());
+                pw.println("        shouldListenForSfps=" + shouldListenForFingerprint(false));
+                if (isSfpsEnrolled()) {
+                    pw.println("        mSfpsRequireScreenOnToAuthPrefEnabled="
+                        + mSfpsRequireScreenOnToAuthPrefEnabled);
+                }
             }
         }
         if (mFaceManager != null && mFaceManager.isHardwareDetected()) {
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
index 46f3d4e..32ce537 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardLogger.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.plugins.log.LogLevel
 import com.android.systemui.plugins.log.LogLevel.DEBUG
 import com.android.systemui.plugins.log.LogLevel.ERROR
+import com.android.systemui.plugins.log.LogLevel.INFO
 import com.android.systemui.plugins.log.LogLevel.VERBOSE
 import com.android.systemui.plugins.log.LogLevel.WARNING
 import com.android.systemui.plugins.log.MessageInitializer
@@ -50,6 +51,14 @@
         buffer.log(TAG, DEBUG, messageInitializer, messagePrinter)
     }
 
+    fun v(msg: String, arg: Any) {
+        buffer.log(TAG, VERBOSE, { str1 = arg.toString() }, { "$msg: $str1" })
+    }
+
+    fun i(msg: String, arg: Any) {
+        buffer.log(TAG, INFO, { str1 = arg.toString() }, { "$msg: $str1" })
+    }
+
     // TODO: remove after b/237743330 is fixed
     fun logStatusBarCalculatedAlpha(alpha: Float) {
         debugLog({ double1 = alpha.toDouble() }, { "Calculated new alpha: $double1" })
diff --git a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
index 31fc320..3308f55 100644
--- a/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
+++ b/packages/SystemUI/src/com/android/keyguard/logging/KeyguardUpdateMonitorLogger.kt
@@ -111,11 +111,10 @@
                 }, { "Face help received, msgId: $int1 msg: $str1" })
     }
 
-    fun logFaceAuthRequested(userInitiatedRequest: Boolean, reason: String?) {
+    fun logFaceAuthRequested(reason: String?) {
         logBuffer.log(TAG, DEBUG, {
-            bool1 = userInitiatedRequest
             str1 = reason
-        }, { "requestFaceAuth() userInitiated=$bool1 reason=$str1" })
+        }, { "requestFaceAuth() reason=$str1" })
     }
 
     fun logFaceAuthSuccess(userId: Int) {
diff --git a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
index d9f44cd..0e7deeb 100644
--- a/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
+++ b/packages/SystemUI/src/com/android/systemui/SystemUIApplication.java
@@ -43,6 +43,7 @@
 import com.android.systemui.dagger.GlobalRootComponent;
 import com.android.systemui.dagger.SysUIComponent;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.util.NotificationChannels;
 
 import java.util.Comparator;
@@ -137,7 +138,7 @@
                     if (mServicesStarted) {
                         final int N = mServices.length;
                         for (int i = 0; i < N; i++) {
-                            mServices[i].onBootCompleted();
+                            notifyBootCompleted(mServices[i]);
                         }
                     }
                 }
@@ -256,7 +257,7 @@
 
         for (i = 0; i < mServices.length; i++) {
             if (mBootCompleteCache.isBootComplete()) {
-                mServices[i].onBootCompleted();
+                notifyBootCompleted(mServices[i]);
             }
 
             mDumpManager.registerDumpable(mServices[i].getClass().getName(), mServices[i]);
@@ -267,7 +268,17 @@
         mServicesStarted = true;
     }
 
-    private void timeInitialization(String clsName, Runnable init, TimingsTraceLog log,
+    private static void notifyBootCompleted(CoreStartable coreStartable) {
+        if (Trace.isEnabled()) {
+            Trace.traceBegin(
+                    Trace.TRACE_TAG_APP,
+                    coreStartable.getClass().getSimpleName() + ".onBootCompleted()");
+        }
+        coreStartable.onBootCompleted();
+        Trace.endSection();
+    }
+
+    private static void timeInitialization(String clsName, Runnable init, TimingsTraceLog log,
             String metricsPrefix) {
         long ti = System.currentTimeMillis();
         log.traceBegin(metricsPrefix + " " + clsName);
@@ -281,28 +292,45 @@
         }
     }
 
-    private CoreStartable startAdditionalStartable(String clsName) {
+    private static CoreStartable startAdditionalStartable(String clsName) {
         CoreStartable startable;
         if (DEBUG) Log.d(TAG, "loading: " + clsName);
+        if (Trace.isEnabled()) {
+            Trace.traceBegin(
+                    Trace.TRACE_TAG_APP, clsName + ".newInstance()");
+        }
         try {
             startable = (CoreStartable) Class.forName(clsName).newInstance();
         } catch (ClassNotFoundException
                 | IllegalAccessException
                 | InstantiationException ex) {
             throw new RuntimeException(ex);
+        } finally {
+            Trace.endSection();
         }
 
         return startStartable(startable);
     }
 
-    private CoreStartable startStartable(String clsName, Provider<CoreStartable> provider) {
+    private static CoreStartable startStartable(String clsName, Provider<CoreStartable> provider) {
         if (DEBUG) Log.d(TAG, "loading: " + clsName);
-        return startStartable(provider.get());
+        if (Trace.isEnabled()) {
+            Trace.traceBegin(
+                    Trace.TRACE_TAG_APP, "Provider<" + clsName + ">.get()");
+        }
+        CoreStartable startable = provider.get();
+        Trace.endSection();
+        return startStartable(startable);
     }
 
-    private CoreStartable startStartable(CoreStartable startable) {
+    private static CoreStartable startStartable(CoreStartable startable) {
         if (DEBUG) Log.d(TAG, "running: " + startable);
+        if (Trace.isEnabled()) {
+            Trace.traceBegin(
+                    Trace.TRACE_TAG_APP, startable.getClass().getSimpleName() + ".start()");
+        }
         startable.start();
+        Trace.endSection();
 
         return startable;
     }
@@ -340,11 +368,25 @@
     @Override
     public void onConfigurationChanged(Configuration newConfig) {
         if (mServicesStarted) {
-            mSysUIComponent.getConfigurationController().onConfigurationChanged(newConfig);
+            ConfigurationController configController = mSysUIComponent.getConfigurationController();
+            if (Trace.isEnabled()) {
+                Trace.traceBegin(
+                        Trace.TRACE_TAG_APP,
+                        configController.getClass().getSimpleName() + ".onConfigurationChanged()");
+            }
+            configController.onConfigurationChanged(newConfig);
+            Trace.endSection();
             int len = mServices.length;
             for (int i = 0; i < len; i++) {
                 if (mServices[i] != null) {
+                    if (Trace.isEnabled()) {
+                        Trace.traceBegin(
+                                Trace.TRACE_TAG_APP,
+                                mServices[i].getClass().getSimpleName()
+                                        + ".onConfigurationChanged()");
+                    }
                     mServices[i].onConfigurationChanged(newConfig);
+                    Trace.endSection();
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java
index 034e96a..4a9807f 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java
@@ -136,11 +136,7 @@
         final Rect draggableBounds = getWindowAvailableBounds();
 
         // Initializes start position for mapping the translation of the menu view.
-        final WindowMetrics windowMetrics = mWindowManager.getCurrentWindowMetrics();
-        final WindowInsets windowInsets = windowMetrics.getWindowInsets();
-        final Insets displayCutoutInsets = windowInsets.getInsetsIgnoringVisibility(
-                WindowInsets.Type.displayCutout());
-        draggableBounds.offset(-displayCutoutInsets.left, -displayCutoutInsets.top);
+        draggableBounds.offsetTo(/* newLeft= */ 0, /* newTop= */ 0);
 
         draggableBounds.top += margin;
         draggableBounds.right -= getMenuWidth();
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
index b1a64ed..c7be907 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
@@ -74,7 +74,8 @@
         params.receiveInsetsIgnoringZOrder = true;
         params.privateFlags |= PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION;
         params.windowAnimations = android.R.style.Animation_Translucent;
-        params.setFitInsetsTypes(WindowInsets.Type.navigationBars());
+        params.setFitInsetsTypes(
+                WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout());
         return params;
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 313ff4157..c7c7f86 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -52,7 +52,7 @@
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
-import android.hardware.fingerprint.IUdfpsHbmListener;
+import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.RemoteException;
@@ -147,7 +147,7 @@
     @NonNull private final WindowManager mWindowManager;
     @NonNull private final DisplayManager mDisplayManager;
     @Nullable private UdfpsController mUdfpsController;
-    @Nullable private IUdfpsHbmListener mUdfpsHbmListener;
+    @Nullable private IUdfpsRefreshRateRequestCallback mUdfpsRefreshRateRequestCallback;
     @Nullable private SidefpsController mSidefpsController;
     @Nullable private IBiometricContextListener mBiometricContextListener;
     @Nullable private UdfpsLogger mUdfpsLogger;
@@ -160,6 +160,7 @@
 
     @NonNull private final SparseBooleanArray mUdfpsEnrolledForUser;
     @NonNull private final SparseBooleanArray mFaceEnrolledForUser;
+    @NonNull private final SparseBooleanArray mSfpsEnrolledForUser;
     @NonNull private final SensorPrivacyManager mSensorPrivacyManager;
     private final WakefulnessLifecycle mWakefulnessLifecycle;
     private boolean mAllFingerprintAuthenticatorsRegistered;
@@ -365,6 +366,15 @@
                 }
             }
         }
+        if (mSidefpsProps == null) {
+            Log.d(TAG, "handleEnrollmentsChanged, mSidefpsProps is null");
+        } else {
+            for (FingerprintSensorPropertiesInternal prop : mSidefpsProps) {
+                if (prop.sensorId == sensorId) {
+                    mSfpsEnrolledForUser.put(userId, hasEnrollments);
+                }
+            }
+        }
         for (Callback cb : mCallbacks) {
             cb.onEnrollmentsChanged(modality);
         }
@@ -722,6 +732,7 @@
         mWindowManager = windowManager;
         mInteractionJankMonitor = jankMonitor;
         mUdfpsEnrolledForUser = new SparseBooleanArray();
+        mSfpsEnrolledForUser = new SparseBooleanArray();
         mFaceEnrolledForUser = new SparseBooleanArray();
         mVibratorHelper = vibrator;
 
@@ -799,17 +810,26 @@
     private void updateUdfpsLocation() {
         if (mUdfpsController != null) {
             final FingerprintSensorPropertiesInternal udfpsProp = mUdfpsProps.get(0);
+
             final Rect previousUdfpsBounds = mUdfpsBounds;
             mUdfpsBounds = udfpsProp.getLocation().getRect();
             mUdfpsBounds.scale(mScaleFactor);
-            mUdfpsController.updateOverlayParams(udfpsProp.sensorId,
-                    new UdfpsOverlayParams(mUdfpsBounds, new Rect(
-                            0, mCachedDisplayInfo.getNaturalHeight() / 2,
-                            mCachedDisplayInfo.getNaturalWidth(),
-                            mCachedDisplayInfo.getNaturalHeight()),
-                            mCachedDisplayInfo.getNaturalWidth(),
-                            mCachedDisplayInfo.getNaturalHeight(), mScaleFactor,
-                            mCachedDisplayInfo.rotation));
+
+            final Rect overlayBounds = new Rect(
+                    0, /* left */
+                    mCachedDisplayInfo.getNaturalHeight() / 2, /* top */
+                    mCachedDisplayInfo.getNaturalWidth(), /* right */
+                    mCachedDisplayInfo.getNaturalHeight() /* botom */);
+
+            final UdfpsOverlayParams overlayParams = new UdfpsOverlayParams(
+                    mUdfpsBounds,
+                    overlayBounds,
+                    mCachedDisplayInfo.getNaturalWidth(),
+                    mCachedDisplayInfo.getNaturalHeight(),
+                    mScaleFactor,
+                    mCachedDisplayInfo.rotation);
+
+            mUdfpsController.updateOverlayParams(udfpsProp, overlayParams);
             if (!Objects.equals(previousUdfpsBounds, mUdfpsBounds)) {
                 for (Callback cb : mCallbacks) {
                     cb.onUdfpsLocationChanged();
@@ -872,21 +892,22 @@
     }
 
     /**
-     * Stores the listener received from {@link com.android.server.display.DisplayModeDirector}.
+     * Stores the callback received from {@link com.android.server.display.DisplayModeDirector}.
      *
-     * DisplayModeDirector implements {@link IUdfpsHbmListener} and registers it with this class by
-     * calling {@link CommandQueue#setUdfpsHbmListener(IUdfpsHbmListener)}.
+     * DisplayModeDirector implements {@link IUdfpsRefreshRateRequestCallback}
+     * and registers it with this class by calling
+     * {@link CommandQueue#setUdfpsRefreshRateCallback(IUdfpsRefreshRateRequestCallback)}.
      */
     @Override
-    public void setUdfpsHbmListener(IUdfpsHbmListener listener) {
-        mUdfpsHbmListener = listener;
+    public void setUdfpsRefreshRateCallback(IUdfpsRefreshRateRequestCallback callback) {
+        mUdfpsRefreshRateRequestCallback = callback;
     }
 
     /**
-     * @return IUdfpsHbmListener that can be set by DisplayModeDirector.
+     * @return IUdfpsRefreshRateRequestCallback that can be set by DisplayModeDirector.
      */
-    @Nullable public IUdfpsHbmListener getUdfpsHbmListener() {
-        return mUdfpsHbmListener;
+    @Nullable public IUdfpsRefreshRateRequestCallback getUdfpsRefreshRateCallback() {
+        return mUdfpsRefreshRateRequestCallback;
     }
 
     @Override
@@ -964,6 +985,11 @@
         return mUdfpsProps;
     }
 
+    @Nullable
+    public List<FingerprintSensorPropertiesInternal> getSfpsProps() {
+        return mSidefpsProps;
+    }
+
     private String getErrorString(@Modality int modality, int error, int vendorCode) {
         switch (modality) {
             case TYPE_FACE:
@@ -1090,6 +1116,17 @@
         return mUdfpsEnrolledForUser.get(userId);
     }
 
+    /**
+     * Whether the passed userId has enrolled SFPS.
+     */
+    public boolean isSfpsEnrolled(int userId) {
+        if (mSidefpsController == null) {
+            return false;
+        }
+
+        return mSfpsEnrolledForUser.get(userId);
+    }
+
     /** If BiometricPrompt is currently being shown to the user. */
     public boolean isShowing() {
         return mCurrentDialog != null;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index 3273d74..3631057 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -23,16 +23,18 @@
 import static com.android.systemui.classifier.Classifier.LOCK_ICON;
 import static com.android.systemui.classifier.Classifier.UDFPS_AUTHENTICATION;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.graphics.Point;
+import android.graphics.Rect;
 import android.hardware.biometrics.BiometricFingerprintConstants;
+import android.hardware.biometrics.SensorProperties;
 import android.hardware.display.DisplayManager;
 import android.hardware.fingerprint.FingerprintManager;
+import android.hardware.fingerprint.FingerprintSensorProperties;
+import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.hardware.fingerprint.IUdfpsOverlayControllerCallback;
 import android.os.Handler;
@@ -50,17 +52,24 @@
 import android.view.WindowManager;
 import android.view.accessibility.AccessibilityManager;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.LatencyTracker;
 import com.android.keyguard.FaceAuthApiRequestReason;
 import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.Dumpable;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.biometrics.dagger.BiometricsBackground;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.doze.DozeReceiver;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.ScreenLifecycle;
+import com.android.systemui.keyguard.domain.interactor.BouncerInteractor;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.ShadeExpansionStateManager;
@@ -75,6 +84,8 @@
 import com.android.systemui.util.concurrency.Execution;
 import com.android.systemui.util.time.SystemClock;
 
+import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.Optional;
 import java.util.Set;
@@ -97,7 +108,7 @@
  */
 @SuppressWarnings("deprecation")
 @SysUISingleton
-public class UdfpsController implements DozeReceiver {
+public class UdfpsController implements DozeReceiver, Dumpable {
     private static final String TAG = "UdfpsController";
     private static final long AOD_INTERRUPT_TIMEOUT_MILLIS = 1000;
 
@@ -119,6 +130,7 @@
     @NonNull private final SystemUIDialogManager mDialogManager;
     @NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     @NonNull private final VibratorHelper mVibrator;
+    @NonNull private final FeatureFlags mFeatureFlags;
     @NonNull private final FalsingManager mFalsingManager;
     @NonNull private final PowerManager mPowerManager;
     @NonNull private final AccessibilityManager mAccessibilityManager;
@@ -130,10 +142,11 @@
     @NonNull private final LatencyTracker mLatencyTracker;
     @VisibleForTesting @NonNull final BiometricDisplayListener mOrientationListener;
     @NonNull private final ActivityLaunchAnimator mActivityLaunchAnimator;
+    @NonNull private final BouncerInteractor mBouncerInteractor;
 
     // Currently the UdfpsController supports a single UDFPS sensor. If devices have multiple
     // sensors, this, in addition to a lot of the code here, will be updated.
-    @VisibleForTesting int mSensorId;
+    @VisibleForTesting @NonNull FingerprintSensorPropertiesInternal mSensorProps;
     @VisibleForTesting @NonNull UdfpsOverlayParams mOverlayParams = new UdfpsOverlayParams();
     // TODO(b/229290039): UDFPS controller should manage its dimensions on its own. Remove this.
     @Nullable private Runnable mAuthControllerUpdateUdfpsLocation;
@@ -198,10 +211,19 @@
         }
     };
 
+    @Override
+    public void dump(@NonNull PrintWriter pw, @NonNull String[] args) {
+        pw.println("mSensorProps=(" + mSensorProps + ")");
+    }
+
     public class UdfpsOverlayController extends IUdfpsOverlayController.Stub {
         @Override
         public void showUdfpsOverlay(long requestId, int sensorId, int reason,
                 @NonNull IUdfpsOverlayControllerCallback callback) {
+            if (mFeatureFlags.isEnabled(Flags.NEW_UDFPS_OVERLAY)) {
+                return;
+            }
+
             mFgExecutor.execute(() -> UdfpsController.this.showUdfpsOverlay(
                     new UdfpsControllerOverlay(mContext, mFingerprintManager, mInflater,
                             mWindowManager, mAccessibilityManager, mStatusBarStateController,
@@ -212,11 +234,16 @@
                             mUnlockedScreenOffAnimationController,
                             mUdfpsDisplayMode, requestId, reason, callback,
                             (view, event, fromUdfpsView) -> onTouch(requestId, event,
-                                    fromUdfpsView), mActivityLaunchAnimator)));
+                                    fromUdfpsView), mActivityLaunchAnimator, mFeatureFlags,
+                            mBouncerInteractor)));
         }
 
         @Override
         public void hideUdfpsOverlay(int sensorId) {
+            if (mFeatureFlags.isEnabled(Flags.NEW_UDFPS_OVERLAY)) {
+                return;
+            }
+
             mFgExecutor.execute(() -> {
                 if (mKeyguardUpdateMonitor.isFingerprintDetectionRunning()) {
                     // if we get here, we expect keyguardUpdateMonitor's fingerprintRunningState
@@ -244,7 +271,7 @@
                     }
                     mAcquiredReceived = true;
                     final UdfpsView view = mOverlay.getOverlayView();
-                    if (view != null) {
+                    if (view != null && isOptical()) {
                         unconfigureDisplay(view);
                     }
                     if (acquiredGood) {
@@ -285,17 +312,42 @@
                 mOverlay.getOverlayView().setDebugMessage(message);
             });
         }
+
+        public Rect getSensorBounds() {
+            return mOverlayParams.getSensorBounds();
+        }
+
+        /**
+         * Passes a mocked MotionEvent to OnTouch.
+         *
+         * @param event MotionEvent to simulate in onTouch
+         */
+        public void debugOnTouch(long requestId, MotionEvent event) {
+            UdfpsController.this.onTouch(requestId, event, false);
+        }
+
+        /**
+         * Debug to run onUiReady
+         */
+        public void debugOnUiReady(long requestId, int sensorId) {
+            if (UdfpsController.this.mAlternateTouchProvider != null) {
+                UdfpsController.this.mAlternateTouchProvider.onUiReady();
+            } else {
+                UdfpsController.this.mFingerprintManager.onUiReady(requestId, sensorId);
+            }
+        }
     }
 
     /**
      * Updates the overlay parameters and reconstructs or redraws the overlay, if necessary.
      *
-     * @param sensorId      sensor for which the overlay is getting updated.
+     * @param sensorProps   sensor for which the overlay is getting updated.
      * @param overlayParams See {@link UdfpsOverlayParams}.
      */
-    public void updateOverlayParams(int sensorId, @NonNull UdfpsOverlayParams overlayParams) {
-        if (sensorId != mSensorId) {
-            mSensorId = sensorId;
+    public void updateOverlayParams(@NonNull FingerprintSensorPropertiesInternal sensorProps,
+            @NonNull UdfpsOverlayParams overlayParams) {
+        if (mSensorProps.sensorId != sensorProps.sensorId) {
+            mSensorProps = sensorProps;
             Log.w(TAG, "updateUdfpsParams | sensorId has changed");
         }
 
@@ -319,7 +371,7 @@
         mAuthControllerUpdateUdfpsLocation = r;
     }
 
-    public void setUdfpsDisplayMode(UdfpsDisplayModeProvider udfpsDisplayMode) {
+    public void setUdfpsDisplayMode(@NonNull UdfpsDisplayModeProvider udfpsDisplayMode) {
         mUdfpsDisplayMode = udfpsDisplayMode;
     }
 
@@ -423,7 +475,6 @@
         }
 
         final UdfpsView udfpsView = mOverlay.getOverlayView();
-        final boolean isDisplayConfigured = udfpsView.isDisplayConfigured();
         boolean handled = false;
         switch (event.getActionMasked()) {
             case MotionEvent.ACTION_OUTSIDE:
@@ -507,15 +558,14 @@
                                 "minor: %.1f, major: %.1f, v: %.1f, exceedsVelocityThreshold: %b",
                                 minor, major, v, exceedsVelocityThreshold);
                         final long sinceLastLog = mSystemClock.elapsedRealtime() - mTouchLogTime;
-                        if (!isDisplayConfigured && !mAcquiredReceived
-                                && !exceedsVelocityThreshold) {
 
+                        if (!mOnFingerDown && !mAcquiredReceived && !exceedsVelocityThreshold) {
                             final float scale = mOverlayParams.getScaleFactor();
                             float scaledMinor = minor / scale;
                             float scaledMajor = major / scale;
-
                             onFingerDown(requestId, scaledTouch.x, scaledTouch.y, scaledMinor,
                                     scaledMajor);
+
                             Log.v(TAG, "onTouch | finger down: " + touchInfo);
                             mTouchLogTime = mSystemClock.elapsedRealtime();
                             handled = true;
@@ -590,6 +640,7 @@
             @NonNull StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             @NonNull DumpManager dumpManager,
             @NonNull KeyguardUpdateMonitor keyguardUpdateMonitor,
+            @NonNull FeatureFlags featureFlags,
             @NonNull FalsingManager falsingManager,
             @NonNull PowerManager powerManager,
             @NonNull AccessibilityManager accessibilityManager,
@@ -608,7 +659,8 @@
             @NonNull LatencyTracker latencyTracker,
             @NonNull ActivityLaunchAnimator activityLaunchAnimator,
             @NonNull Optional<AlternateUdfpsTouchProvider> alternateTouchProvider,
-            @BiometricsBackground Executor biometricsExecutor) {
+            @NonNull @BiometricsBackground Executor biometricsExecutor,
+            @NonNull BouncerInteractor bouncerInteractor) {
         mContext = context;
         mExecution = execution;
         mVibrator = vibrator;
@@ -625,6 +677,7 @@
         mDumpManager = dumpManager;
         mDialogManager = dialogManager;
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
+        mFeatureFlags = featureFlags;
         mFalsingManager = falsingManager;
         mPowerManager = powerManager;
         mAccessibilityManager = accessibilityManager;
@@ -637,7 +690,18 @@
         mLatencyTracker = latencyTracker;
         mActivityLaunchAnimator = activityLaunchAnimator;
         mAlternateTouchProvider = alternateTouchProvider.orElse(null);
+        mSensorProps = new FingerprintSensorPropertiesInternal(
+                -1 /* sensorId */,
+                SensorProperties.STRENGTH_CONVENIENCE,
+                0 /* maxEnrollmentsPerUser */,
+                new ArrayList<>() /* componentInfo */,
+                FingerprintSensorProperties.TYPE_UNKNOWN,
+                false /* resetLockoutRequiresHardwareAuthToken */);
+
         mBiometricExecutor = biometricsExecutor;
+        mBouncerInteractor = bouncerInteractor;
+
+        mDumpManager.registerDumpable(TAG, this);
 
         mOrientationListener = new BiometricDisplayListener(
                 context,
@@ -834,6 +898,10 @@
         mIsAodInterruptActive = false;
     }
 
+    private boolean isOptical() {
+        return mSensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL;
+    }
+
     public boolean isFingerDown() {
         return mOnFingerDown;
     }
@@ -850,7 +918,9 @@
                     + " current: " + mOverlay.getRequestId());
             return;
         }
-        mLatencyTracker.onActionStart(LatencyTracker.ACTION_UDFPS_ILLUMINATE);
+        if (isOptical()) {
+            mLatencyTracker.onActionStart(LatencyTracker.ACTION_UDFPS_ILLUMINATE);
+        }
         // Refresh screen timeout and boost process priority if possible.
         mPowerManager.userActivity(mSystemClock.uptimeMillis(),
                 PowerManager.USER_ACTIVITY_EVENT_TOUCH, 0);
@@ -859,9 +929,7 @@
             playStartHaptic();
 
             if (!mKeyguardUpdateMonitor.isFaceDetectionRunning()) {
-                mKeyguardUpdateMonitor.requestFaceAuth(
-                        /* userInitiatedRequest */ false,
-                        FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
+                mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
             }
         }
         mOnFingerDown = true;
@@ -875,11 +943,11 @@
                 }
             });
         } else {
-            mFingerprintManager.onPointerDown(requestId, mSensorId, x, y, minor, major);
+            mFingerprintManager.onPointerDown(requestId, mSensorProps.sensorId, x, y, minor, major);
         }
         Trace.endAsyncSection("UdfpsController.e2e.onPointerDown", 0);
         final UdfpsView view = mOverlay.getOverlayView();
-        if (view != null) {
+        if (view != null && isOptical()) {
             view.configureDisplay(() -> {
                 if (mAlternateTouchProvider != null) {
                     mBiometricExecutor.execute(() -> {
@@ -887,7 +955,7 @@
                         mLatencyTracker.onActionEnd(LatencyTracker.ACTION_UDFPS_ILLUMINATE);
                     });
                 } else {
-                    mFingerprintManager.onUiReady(requestId, mSensorId);
+                    mFingerprintManager.onUiReady(requestId, mSensorProps.sensorId);
                     mLatencyTracker.onActionEnd(LatencyTracker.ACTION_UDFPS_ILLUMINATE);
                 }
             });
@@ -913,15 +981,16 @@
                     }
                 });
             } else {
-                mFingerprintManager.onPointerUp(requestId, mSensorId);
+                mFingerprintManager.onPointerUp(requestId, mSensorProps.sensorId);
             }
             for (Callback cb : mCallbacks) {
                 cb.onFingerUp();
             }
         }
         mOnFingerDown = false;
-        unconfigureDisplay(view);
-
+        if (isOptical()) {
+            unconfigureDisplay(view);
+        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 7d01096..d70861a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -48,6 +48,8 @@
 import com.android.systemui.R
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.keyguard.domain.interactor.BouncerInteractor
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.shade.ShadeExpansionStateManager
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
@@ -70,29 +72,31 @@
  */
 @UiThread
 class UdfpsControllerOverlay @JvmOverloads constructor(
-    private val context: Context,
-    fingerprintManager: FingerprintManager,
-    private val inflater: LayoutInflater,
-    private val windowManager: WindowManager,
-    private val accessibilityManager: AccessibilityManager,
-    private val statusBarStateController: StatusBarStateController,
-    private val shadeExpansionStateManager: ShadeExpansionStateManager,
-    private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
-    private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
-    private val dialogManager: SystemUIDialogManager,
-    private val dumpManager: DumpManager,
-    private val transitionController: LockscreenShadeTransitionController,
-    private val configurationController: ConfigurationController,
-    private val systemClock: SystemClock,
-    private val keyguardStateController: KeyguardStateController,
-    private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController,
-    private var udfpsDisplayModeProvider: UdfpsDisplayModeProvider,
-    val requestId: Long,
-    @ShowReason val requestReason: Int,
-    private val controllerCallback: IUdfpsOverlayControllerCallback,
-    private val onTouch: (View, MotionEvent, Boolean) -> Boolean,
-    private val activityLaunchAnimator: ActivityLaunchAnimator,
-    private val isDebuggable: Boolean = Build.IS_DEBUGGABLE
+        private val context: Context,
+        fingerprintManager: FingerprintManager,
+        private val inflater: LayoutInflater,
+        private val windowManager: WindowManager,
+        private val accessibilityManager: AccessibilityManager,
+        private val statusBarStateController: StatusBarStateController,
+        private val shadeExpansionStateManager: ShadeExpansionStateManager,
+        private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
+        private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+        private val dialogManager: SystemUIDialogManager,
+        private val dumpManager: DumpManager,
+        private val transitionController: LockscreenShadeTransitionController,
+        private val configurationController: ConfigurationController,
+        private val systemClock: SystemClock,
+        private val keyguardStateController: KeyguardStateController,
+        private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController,
+        private var udfpsDisplayModeProvider: UdfpsDisplayModeProvider,
+        val requestId: Long,
+        @ShowReason val requestReason: Int,
+        private val controllerCallback: IUdfpsOverlayControllerCallback,
+        private val onTouch: (View, MotionEvent, Boolean) -> Boolean,
+        private val activityLaunchAnimator: ActivityLaunchAnimator,
+        private val featureFlags: FeatureFlags,
+        private val bouncerInteractor: BouncerInteractor,
+        private val isDebuggable: Boolean = Build.IS_DEBUGGABLE
 ) {
     /** The view, when [isShowing], or null. */
     var overlayView: UdfpsView? = null
@@ -246,7 +250,9 @@
                     unlockedScreenOffAnimationController,
                     dialogManager,
                     controller,
-                    activityLaunchAnimator
+                    activityLaunchAnimator,
+                    featureFlags,
+                    bouncerInteractor
                 )
             }
             REASON_AUTH_BP -> {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDisplayMode.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDisplayMode.kt
index b80b8a0..670a8e6 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDisplayMode.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsDisplayMode.kt
@@ -46,7 +46,7 @@
             logger.e(TAG, "enable | already requested")
             return
         }
-        if (authController.udfpsHbmListener == null) {
+        if (authController.udfpsRefreshRateCallback == null) {
             logger.e(TAG, "enable | mDisplayManagerCallback is null")
             return
         }
@@ -60,7 +60,7 @@
         try {
             // This method is a misnomer. It has nothing to do with HBM, its purpose is to set
             // the appropriate display refresh rate.
-            authController.udfpsHbmListener!!.onHbmEnabled(request.displayId)
+            authController.udfpsRefreshRateCallback!!.onRequestEnabled(request.displayId)
             logger.v(TAG, "enable | requested optimal refresh rate for UDFPS")
         } catch (e: RemoteException) {
             logger.e(TAG, "enable", e)
@@ -84,7 +84,7 @@
 
         try {
             // Allow DisplayManager to unset the UDFPS refresh rate.
-            authController.udfpsHbmListener!!.onHbmDisabled(request.displayId)
+            authController.udfpsRefreshRateCallback!!.onRequestDisabled(request.displayId)
             logger.v(TAG, "disable | removed the UDFPS refresh rate request")
         } catch (e: RemoteException) {
             logger.e(TAG, "disable", e)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
deleted file mode 100644
index 4d7f89d..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.java
+++ /dev/null
@@ -1,548 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics;
-
-import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
-
-import android.animation.ValueAnimator;
-import android.annotation.NonNull;
-import android.content.res.Configuration;
-import android.util.MathUtils;
-import android.view.MotionEvent;
-
-import com.android.keyguard.BouncerPanelExpansionCalculator;
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.R;
-import com.android.systemui.animation.ActivityLaunchAnimator;
-import com.android.systemui.animation.Interpolators;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shade.ShadeExpansionChangeEvent;
-import com.android.systemui.shade.ShadeExpansionListener;
-import com.android.systemui.shade.ShadeExpansionStateManager;
-import com.android.systemui.statusbar.LockscreenShadeTransitionController;
-import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
-import com.android.systemui.statusbar.phone.KeyguardBouncer;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.phone.SystemUIDialogManager;
-import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.util.time.SystemClock;
-
-import java.io.PrintWriter;
-
-/**
- * Class that coordinates non-HBM animations during keyguard authentication.
- */
-public class UdfpsKeyguardViewController extends UdfpsAnimationViewController<UdfpsKeyguardView> {
-    public static final String TAG = "UdfpsKeyguardViewCtrl";
-    @NonNull private final StatusBarKeyguardViewManager mKeyguardViewManager;
-    @NonNull private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    @NonNull private final LockscreenShadeTransitionController mLockScreenShadeTransitionController;
-    @NonNull private final ConfigurationController mConfigurationController;
-    @NonNull private final SystemClock mSystemClock;
-    @NonNull private final KeyguardStateController mKeyguardStateController;
-    @NonNull private final UdfpsController mUdfpsController;
-    @NonNull private final UnlockedScreenOffAnimationController
-            mUnlockedScreenOffAnimationController;
-    @NonNull private final ActivityLaunchAnimator mActivityLaunchAnimator;
-    private final ValueAnimator mUnlockedScreenOffDozeAnimator = ValueAnimator.ofFloat(0f, 1f);
-
-    private boolean mShowingUdfpsBouncer;
-    private boolean mUdfpsRequested;
-    private float mQsExpansion;
-    private boolean mFaceDetectRunning;
-    private int mStatusBarState;
-    private float mTransitionToFullShadeProgress;
-    private float mLastDozeAmount;
-    private long mLastUdfpsBouncerShowTime = -1;
-    private float mPanelExpansionFraction;
-    private boolean mLaunchTransitionFadingAway;
-    private boolean mIsLaunchingActivity;
-    private float mActivityLaunchProgress;
-
-    /**
-     * hidden amount of pin/pattern/password bouncer
-     * {@link KeyguardBouncer#EXPANSION_VISIBLE} (0f) to
-     * {@link KeyguardBouncer#EXPANSION_HIDDEN} (1f)
-     */
-    private float mInputBouncerHiddenAmount;
-    private boolean mIsGenericBouncerShowing; // whether UDFPS bouncer or input bouncer is visible
-
-    protected UdfpsKeyguardViewController(
-            @NonNull UdfpsKeyguardView view,
-            @NonNull StatusBarStateController statusBarStateController,
-            @NonNull ShadeExpansionStateManager shadeExpansionStateManager,
-            @NonNull StatusBarKeyguardViewManager statusBarKeyguardViewManager,
-            @NonNull KeyguardUpdateMonitor keyguardUpdateMonitor,
-            @NonNull DumpManager dumpManager,
-            @NonNull LockscreenShadeTransitionController transitionController,
-            @NonNull ConfigurationController configurationController,
-            @NonNull SystemClock systemClock,
-            @NonNull KeyguardStateController keyguardStateController,
-            @NonNull UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
-            @NonNull SystemUIDialogManager systemUIDialogManager,
-            @NonNull UdfpsController udfpsController,
-            @NonNull ActivityLaunchAnimator activityLaunchAnimator) {
-        super(view, statusBarStateController, shadeExpansionStateManager, systemUIDialogManager,
-                dumpManager);
-        mKeyguardViewManager = statusBarKeyguardViewManager;
-        mKeyguardUpdateMonitor = keyguardUpdateMonitor;
-        mLockScreenShadeTransitionController = transitionController;
-        mConfigurationController = configurationController;
-        mSystemClock = systemClock;
-        mKeyguardStateController = keyguardStateController;
-        mUdfpsController = udfpsController;
-        mUnlockedScreenOffAnimationController = unlockedScreenOffAnimationController;
-        mActivityLaunchAnimator = activityLaunchAnimator;
-
-        mUnlockedScreenOffDozeAnimator.setDuration(StackStateAnimator.ANIMATION_DURATION_STANDARD);
-        mUnlockedScreenOffDozeAnimator.setInterpolator(Interpolators.ALPHA_IN);
-        mUnlockedScreenOffDozeAnimator.addUpdateListener(
-                new ValueAnimator.AnimatorUpdateListener() {
-                    @Override
-                    public void onAnimationUpdate(ValueAnimator animation) {
-                        mView.onDozeAmountChanged(
-                                animation.getAnimatedFraction(),
-                                (float) animation.getAnimatedValue(),
-                                UdfpsKeyguardView.ANIMATION_UNLOCKED_SCREEN_OFF);
-                    }
-                });
-    }
-
-    @Override
-    @NonNull protected String getTag() {
-        return "UdfpsKeyguardViewController";
-    }
-
-    @Override
-    public void onInit() {
-        super.onInit();
-        mKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor);
-    }
-
-    @Override
-    protected void onViewAttached() {
-        super.onViewAttached();
-        final float dozeAmount = getStatusBarStateController().getDozeAmount();
-        mLastDozeAmount = dozeAmount;
-        mStateListener.onDozeAmountChanged(dozeAmount, dozeAmount);
-        getStatusBarStateController().addCallback(mStateListener);
-
-        mUdfpsRequested = false;
-
-        mLaunchTransitionFadingAway = mKeyguardStateController.isLaunchTransitionFadingAway();
-        mKeyguardStateController.addCallback(mKeyguardStateControllerCallback);
-        mStatusBarState = getStatusBarStateController().getState();
-        mQsExpansion = mKeyguardViewManager.getQsExpansion();
-        updateGenericBouncerVisibility();
-        mConfigurationController.addCallback(mConfigurationListener);
-        getShadeExpansionStateManager().addExpansionListener(mShadeExpansionListener);
-        updateScaleFactor();
-        mView.updatePadding();
-        updateAlpha();
-        updatePauseAuth();
-
-        mKeyguardViewManager.setAlternateAuthInterceptor(mAlternateAuthInterceptor);
-        mLockScreenShadeTransitionController.setUdfpsKeyguardViewController(this);
-        mActivityLaunchAnimator.addListener(mActivityLaunchAnimatorListener);
-    }
-
-    @Override
-    protected void onViewDetached() {
-        super.onViewDetached();
-        mFaceDetectRunning = false;
-
-        mKeyguardStateController.removeCallback(mKeyguardStateControllerCallback);
-        getStatusBarStateController().removeCallback(mStateListener);
-        mKeyguardViewManager.removeAlternateAuthInterceptor(mAlternateAuthInterceptor);
-        mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false);
-        mConfigurationController.removeCallback(mConfigurationListener);
-        getShadeExpansionStateManager().removeExpansionListener(mShadeExpansionListener);
-        if (mLockScreenShadeTransitionController.getUdfpsKeyguardViewController() == this) {
-            mLockScreenShadeTransitionController.setUdfpsKeyguardViewController(null);
-        }
-        mActivityLaunchAnimator.removeListener(mActivityLaunchAnimatorListener);
-    }
-
-    @Override
-    public void dump(PrintWriter pw, String[] args) {
-        super.dump(pw, args);
-        pw.println("mShowingUdfpsBouncer=" + mShowingUdfpsBouncer);
-        pw.println("mFaceDetectRunning=" + mFaceDetectRunning);
-        pw.println("mStatusBarState=" + StatusBarState.toString(mStatusBarState));
-        pw.println("mTransitionToFullShadeProgress=" + mTransitionToFullShadeProgress);
-        pw.println("mQsExpansion=" + mQsExpansion);
-        pw.println("mIsGenericBouncerShowing=" + mIsGenericBouncerShowing);
-        pw.println("mInputBouncerHiddenAmount=" + mInputBouncerHiddenAmount);
-        pw.println("mPanelExpansionFraction=" + mPanelExpansionFraction);
-        pw.println("unpausedAlpha=" + mView.getUnpausedAlpha());
-        pw.println("mUdfpsRequested=" + mUdfpsRequested);
-        pw.println("mLaunchTransitionFadingAway=" + mLaunchTransitionFadingAway);
-        pw.println("mLastDozeAmount=" + mLastDozeAmount);
-
-        mView.dump(pw);
-    }
-
-    /**
-     * Overrides non-bouncer show logic in shouldPauseAuth to still show icon.
-     * @return whether the udfpsBouncer has been newly shown or hidden
-     */
-    private boolean showUdfpsBouncer(boolean show) {
-        if (mShowingUdfpsBouncer == show) {
-            return false;
-        }
-
-        boolean udfpsAffordanceWasNotShowing = shouldPauseAuth();
-        mShowingUdfpsBouncer = show;
-        if (mShowingUdfpsBouncer) {
-            mLastUdfpsBouncerShowTime = mSystemClock.uptimeMillis();
-        }
-        if (mShowingUdfpsBouncer) {
-            if (udfpsAffordanceWasNotShowing) {
-                mView.animateInUdfpsBouncer(null);
-            }
-
-            if (mKeyguardStateController.isOccluded()) {
-                mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(true);
-            }
-
-            mView.announceForAccessibility(mView.getContext().getString(
-                    R.string.accessibility_fingerprint_bouncer));
-        } else {
-            mKeyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false);
-        }
-
-        updateGenericBouncerVisibility();
-        updateAlpha();
-        updatePauseAuth();
-        return true;
-    }
-
-    /**
-     * Returns true if the fingerprint manager is running but we want to temporarily pause
-     * authentication. On the keyguard, we may want to show udfps when the shade
-     * is expanded, so this can be overridden with the showBouncer method.
-     */
-    public boolean shouldPauseAuth() {
-        if (mShowingUdfpsBouncer) {
-            return false;
-        }
-
-        if (mUdfpsRequested && !getNotificationShadeVisible()
-                && (!mIsGenericBouncerShowing
-                || mInputBouncerHiddenAmount != KeyguardBouncer.EXPANSION_VISIBLE)
-                && mKeyguardStateController.isShowing()) {
-            return false;
-        }
-
-        if (mLaunchTransitionFadingAway) {
-            return true;
-        }
-
-        // Only pause auth if we're not on the keyguard AND we're not transitioning to doze
-        // (ie: dozeAmount = 0f). For the UnlockedScreenOffAnimation, the statusBarState is
-        // delayed. However, we still animate in the UDFPS affordance with the 
-        // mUnlockedScreenOffDozeAnimator.
-        if (mStatusBarState != KEYGUARD && mLastDozeAmount == 0f) {
-            return true;
-        }
-
-        if (mInputBouncerHiddenAmount < .5f) {
-            return true;
-        }
-
-        if (mView.getUnpausedAlpha() < (255 * .1)) {
-            return true;
-        }
-
-        return false;
-    }
-
-    @Override
-    public boolean listenForTouchesOutsideView() {
-        return true;
-    }
-
-    @Override
-    public void onTouchOutsideView() {
-        maybeShowInputBouncer();
-    }
-
-    /**
-     * If we were previously showing the udfps bouncer, hide it and instead show the regular
-     * (pin/pattern/password) bouncer.
-     *
-     * Does nothing if we weren't previously showing the UDFPS bouncer.
-     */
-    private void maybeShowInputBouncer() {
-        if (mShowingUdfpsBouncer && hasUdfpsBouncerShownWithMinTime()) {
-            mKeyguardViewManager.showBouncer(true);
-        }
-    }
-
-    /**
-     * Whether the udfps bouncer has shown for at least 200ms before allowing touches outside
-     * of the udfps icon area to dismiss the udfps bouncer and show the pin/pattern/password
-     * bouncer.
-     */
-    private boolean hasUdfpsBouncerShownWithMinTime() {
-        return (mSystemClock.uptimeMillis() - mLastUdfpsBouncerShowTime) > 200;
-    }
-
-    /**
-     * Set the progress we're currently transitioning to the full shade. 0.0f means we're not
-     * transitioning yet, while 1.0f means we've fully dragged down.
-     *
-     * For example, start swiping down to expand the notification shade from the empty space in
-     * the middle of the lock screen.
-     */
-    public void setTransitionToFullShadeProgress(float progress) {
-        mTransitionToFullShadeProgress = progress;
-        updateAlpha();
-    }
-
-    /**
-     * Update alpha for the UDFPS lock screen affordance. The AoD UDFPS visual affordance's
-     * alpha is based on the doze amount.
-     */
-    @Override
-    public void updateAlpha() {
-        // Fade icon on transitions to showing the status bar or bouncer, but if mUdfpsRequested,
-        // then the keyguard is occluded by some application - so instead use the input bouncer
-        // hidden amount to determine the fade.
-        float expansion = mUdfpsRequested ? mInputBouncerHiddenAmount : mPanelExpansionFraction;
-
-        int alpha = mShowingUdfpsBouncer ? 255
-                : (int) MathUtils.constrain(
-                    MathUtils.map(.5f, .9f, 0f, 255f, expansion),
-                    0f, 255f);
-
-        if (!mShowingUdfpsBouncer) {
-            // swipe from top of the lockscreen to expand full QS:
-            alpha *= (1.0f - Interpolators.EMPHASIZED_DECELERATE.getInterpolation(mQsExpansion));
-
-            // swipe from the middle (empty space) of lockscreen to expand the notification shade:
-            alpha *= (1.0f - mTransitionToFullShadeProgress);
-
-            // Fade out the icon if we are animating an activity launch over the lockscreen and the
-            // activity didn't request the UDFPS.
-            if (mIsLaunchingActivity && !mUdfpsRequested) {
-                alpha *= (1.0f - mActivityLaunchProgress);
-            }
-
-            // Fade out alpha when a dialog is shown
-            // Fade in alpha when a dialog is hidden
-            alpha *= mView.getDialogSuggestedAlpha();
-        }
-        mView.setUnpausedAlpha(alpha);
-    }
-
-    /**
-     * Updates mIsGenericBouncerShowing (whether any bouncer is showing) and updates the
-     * mInputBouncerHiddenAmount to reflect whether the input bouncer is fully showing or not.
-     */
-    private void updateGenericBouncerVisibility() {
-        mIsGenericBouncerShowing = mKeyguardViewManager.isBouncerShowing(); // includes altBouncer
-        final boolean altBouncerShowing = mKeyguardViewManager.isShowingAlternateAuth();
-        if (altBouncerShowing || !mKeyguardViewManager.bouncerIsOrWillBeShowing()) {
-            mInputBouncerHiddenAmount = 1f;
-        } else if (mIsGenericBouncerShowing) {
-            // input bouncer is fully showing
-            mInputBouncerHiddenAmount = 0f;
-        }
-    }
-
-    /**
-     * Update the scale factor based on the device's resolution.
-     */
-    private void updateScaleFactor() {
-        if (mUdfpsController != null && mUdfpsController.mOverlayParams != null) {
-            mView.setScaleFactor(mUdfpsController.mOverlayParams.getScaleFactor());
-        }
-    }
-
-    private final StatusBarStateController.StateListener mStateListener =
-            new StatusBarStateController.StateListener() {
-        @Override
-        public void onDozeAmountChanged(float linear, float eased) {
-            if (mLastDozeAmount < linear) {
-                showUdfpsBouncer(false);
-            }
-            mUnlockedScreenOffDozeAnimator.cancel();
-            final boolean animatingFromUnlockedScreenOff =
-                    mUnlockedScreenOffAnimationController.isAnimationPlaying();
-            if (animatingFromUnlockedScreenOff && linear != 0f) {
-                // we manually animate the fade in of the UDFPS icon since the unlocked
-                // screen off animation prevents the doze amounts to be incrementally eased in
-                mUnlockedScreenOffDozeAnimator.start();
-            } else {
-                mView.onDozeAmountChanged(linear, eased,
-                        UdfpsKeyguardView.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN);
-            }
-
-            mLastDozeAmount = linear;
-            updatePauseAuth();
-        }
-
-        @Override
-        public void onStateChanged(int statusBarState) {
-            mStatusBarState = statusBarState;
-            updateAlpha();
-            updatePauseAuth();
-        }
-    };
-
-    private final StatusBarKeyguardViewManager.AlternateAuthInterceptor mAlternateAuthInterceptor =
-            new StatusBarKeyguardViewManager.AlternateAuthInterceptor() {
-                @Override
-                public boolean showAlternateAuthBouncer() {
-                    return showUdfpsBouncer(true);
-                }
-
-                @Override
-                public boolean hideAlternateAuthBouncer() {
-                    return showUdfpsBouncer(false);
-                }
-
-                @Override
-                public boolean isShowingAlternateAuthBouncer() {
-                    return mShowingUdfpsBouncer;
-                }
-
-                @Override
-                public void requestUdfps(boolean request, int color) {
-                    mUdfpsRequested = request;
-                    mView.requestUdfps(request, color);
-                    updateAlpha();
-                    updatePauseAuth();
-                }
-
-                @Override
-                public boolean isAnimating() {
-                    return false;
-                }
-
-                /**
-                 * Set the amount qs is expanded. Forxample, swipe down from the top of the
-                 * lock screen to start the full QS expansion.
-                 */
-                @Override
-                public void setQsExpansion(float qsExpansion) {
-                    mQsExpansion = qsExpansion;
-                    updateAlpha();
-                    updatePauseAuth();
-                }
-
-                @Override
-                public boolean onTouch(MotionEvent event) {
-                    if (mTransitionToFullShadeProgress != 0) {
-                        return false;
-                    }
-                    return mUdfpsController.onTouch(event);
-                }
-
-                @Override
-                public void setBouncerExpansionChanged(float expansion) {
-                    mInputBouncerHiddenAmount = expansion;
-                    updateAlpha();
-                    updatePauseAuth();
-                }
-
-                /**
-                 * Only called on primary auth bouncer changes, not on whether the UDFPS bouncer
-                 * visibility changes.
-                 */
-                @Override
-                public void onBouncerVisibilityChanged() {
-                    updateGenericBouncerVisibility();
-                    updateAlpha();
-                    updatePauseAuth();
-                }
-
-                @Override
-                public void dump(PrintWriter pw) {
-                    pw.println(getTag());
-                }
-            };
-
-    private final ConfigurationController.ConfigurationListener mConfigurationListener =
-            new ConfigurationController.ConfigurationListener() {
-                @Override
-                public void onUiModeChanged() {
-                    mView.updateColor();
-                }
-
-                @Override
-                public void onThemeChanged() {
-                    mView.updateColor();
-                }
-
-                @Override
-                public void onConfigChanged(Configuration newConfig) {
-                    updateScaleFactor();
-                    mView.updatePadding();
-                    mView.updateColor();
-                }
-            };
-
-    private final ShadeExpansionListener mShadeExpansionListener = new ShadeExpansionListener() {
-        @Override
-        public void onPanelExpansionChanged(ShadeExpansionChangeEvent event) {
-            float fraction = event.getFraction();
-            mPanelExpansionFraction =
-                    mKeyguardViewManager.isBouncerInTransit() ? BouncerPanelExpansionCalculator
-                            .aboutToShowBouncerProgress(fraction) : fraction;
-            updateAlpha();
-            updatePauseAuth();
-        }
-    };
-
-    private final KeyguardStateController.Callback mKeyguardStateControllerCallback =
-            new KeyguardStateController.Callback() {
-                @Override
-                public void onLaunchTransitionFadingAwayChanged() {
-                    mLaunchTransitionFadingAway =
-                            mKeyguardStateController.isLaunchTransitionFadingAway();
-                    updatePauseAuth();
-                }
-            };
-
-    private final ActivityLaunchAnimator.Listener mActivityLaunchAnimatorListener =
-            new ActivityLaunchAnimator.Listener() {
-                @Override
-                public void onLaunchAnimationStart() {
-                    mIsLaunchingActivity = true;
-                    mActivityLaunchProgress = 0f;
-                    updateAlpha();
-                }
-
-                @Override
-                public void onLaunchAnimationEnd() {
-                    mIsLaunchingActivity = false;
-                    updateAlpha();
-                }
-
-                @Override
-                public void onLaunchAnimationProgress(float linearProgress) {
-                    mActivityLaunchProgress = linearProgress;
-                    updateAlpha();
-                }
-            };
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
new file mode 100644
index 0000000..5bae2dc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewController.kt
@@ -0,0 +1,550 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics
+
+import android.animation.ValueAnimator
+import android.content.res.Configuration
+import android.util.MathUtils
+import android.view.MotionEvent
+import androidx.annotation.VisibleForTesting
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.R
+import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.domain.interactor.BouncerInteractor
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.shade.ShadeExpansionListener
+import com.android.systemui.shade.ShadeExpansionStateManager
+import com.android.systemui.statusbar.LockscreenShadeTransitionController
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.notification.stack.StackStateAnimator
+import com.android.systemui.statusbar.phone.KeyguardBouncer
+import com.android.systemui.statusbar.phone.KeyguardBouncer.BouncerExpansionCallback
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.AlternateAuthInterceptor
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager.KeyguardViewManagerCallback
+import com.android.systemui.statusbar.phone.SystemUIDialogManager
+import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController
+import com.android.systemui.statusbar.policy.ConfigurationController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.time.SystemClock
+import java.io.PrintWriter
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+/** Class that coordinates non-HBM animations during keyguard authentication. */
+open class UdfpsKeyguardViewController
+constructor(
+    private val view: UdfpsKeyguardView,
+    statusBarStateController: StatusBarStateController,
+    shadeExpansionStateManager: ShadeExpansionStateManager,
+    private val keyguardViewManager: StatusBarKeyguardViewManager,
+    private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+    dumpManager: DumpManager,
+    private val lockScreenShadeTransitionController: LockscreenShadeTransitionController,
+    private val configurationController: ConfigurationController,
+    private val systemClock: SystemClock,
+    private val keyguardStateController: KeyguardStateController,
+    private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController,
+    systemUIDialogManager: SystemUIDialogManager,
+    private val udfpsController: UdfpsController,
+    private val activityLaunchAnimator: ActivityLaunchAnimator,
+    featureFlags: FeatureFlags,
+    private val bouncerInteractor: BouncerInteractor
+) :
+    UdfpsAnimationViewController<UdfpsKeyguardView>(
+        view,
+        statusBarStateController,
+        shadeExpansionStateManager,
+        systemUIDialogManager,
+        dumpManager
+    ) {
+    private val isModernBouncerEnabled: Boolean = featureFlags.isEnabled(Flags.MODERN_BOUNCER)
+    private var showingUdfpsBouncer = false
+    private var udfpsRequested = false
+    private var qsExpansion = 0f
+    private var faceDetectRunning = false
+    private var statusBarState = 0
+    private var transitionToFullShadeProgress = 0f
+    private var lastDozeAmount = 0f
+    private var lastUdfpsBouncerShowTime: Long = -1
+    private var panelExpansionFraction = 0f
+    private var launchTransitionFadingAway = false
+    private var isLaunchingActivity = false
+    private var activityLaunchProgress = 0f
+    private val unlockedScreenOffDozeAnimator =
+        ValueAnimator.ofFloat(0f, 1f).apply {
+            duration = StackStateAnimator.ANIMATION_DURATION_STANDARD.toLong()
+            interpolator = Interpolators.ALPHA_IN
+            addUpdateListener { animation ->
+                view.onDozeAmountChanged(
+                    animation.animatedFraction,
+                    animation.animatedValue as Float,
+                    UdfpsKeyguardView.ANIMATION_UNLOCKED_SCREEN_OFF
+                )
+            }
+        }
+    /**
+     * Hidden amount of input (pin/pattern/password) bouncer. This is used
+     * [KeyguardBouncer.EXPANSION_VISIBLE] (0f) to [KeyguardBouncer.EXPANSION_HIDDEN] (1f). Only
+     * used for the non-modernBouncer.
+     */
+    private var inputBouncerHiddenAmount = KeyguardBouncer.EXPANSION_HIDDEN
+    private var inputBouncerExpansion = 0f // only used for modernBouncer
+
+    private val stateListener: StatusBarStateController.StateListener =
+        object : StatusBarStateController.StateListener {
+            override fun onDozeAmountChanged(linear: Float, eased: Float) {
+                if (lastDozeAmount < linear) {
+                    showUdfpsBouncer(false)
+                }
+                unlockedScreenOffDozeAnimator.cancel()
+                val animatingFromUnlockedScreenOff =
+                    unlockedScreenOffAnimationController.isAnimationPlaying()
+                if (animatingFromUnlockedScreenOff && linear != 0f) {
+                    // we manually animate the fade in of the UDFPS icon since the unlocked
+                    // screen off animation prevents the doze amounts to be incrementally eased in
+                    unlockedScreenOffDozeAnimator.start()
+                } else {
+                    view.onDozeAmountChanged(
+                        linear,
+                        eased,
+                        UdfpsKeyguardView.ANIMATION_BETWEEN_AOD_AND_LOCKSCREEN
+                    )
+                }
+                lastDozeAmount = linear
+                updatePauseAuth()
+            }
+
+            override fun onStateChanged(statusBarState: Int) {
+                this@UdfpsKeyguardViewController.statusBarState = statusBarState
+                updateAlpha()
+                updatePauseAuth()
+            }
+        }
+
+    private val bouncerExpansionCallback: BouncerExpansionCallback =
+        object : BouncerExpansionCallback {
+            override fun onExpansionChanged(expansion: Float) {
+                inputBouncerHiddenAmount = expansion
+                updateAlpha()
+                updatePauseAuth()
+            }
+
+            override fun onVisibilityChanged(isVisible: Boolean) {
+                updateBouncerHiddenAmount()
+                updateAlpha()
+                updatePauseAuth()
+            }
+        }
+
+    private val configurationListener: ConfigurationController.ConfigurationListener =
+        object : ConfigurationController.ConfigurationListener {
+            override fun onUiModeChanged() {
+                view.updateColor()
+            }
+
+            override fun onThemeChanged() {
+                view.updateColor()
+            }
+
+            override fun onConfigChanged(newConfig: Configuration) {
+                updateScaleFactor()
+                view.updatePadding()
+                view.updateColor()
+            }
+        }
+
+    private val shadeExpansionListener = ShadeExpansionListener { (fraction) ->
+        panelExpansionFraction =
+            if (keyguardViewManager.isBouncerInTransit) {
+                aboutToShowBouncerProgress(fraction)
+            } else {
+                fraction
+            }
+        updateAlpha()
+        updatePauseAuth()
+    }
+
+    private val keyguardStateControllerCallback: KeyguardStateController.Callback =
+        object : KeyguardStateController.Callback {
+            override fun onLaunchTransitionFadingAwayChanged() {
+                launchTransitionFadingAway = keyguardStateController.isLaunchTransitionFadingAway
+                updatePauseAuth()
+            }
+        }
+
+    private val activityLaunchAnimatorListener: ActivityLaunchAnimator.Listener =
+        object : ActivityLaunchAnimator.Listener {
+            override fun onLaunchAnimationStart() {
+                isLaunchingActivity = true
+                activityLaunchProgress = 0f
+                updateAlpha()
+            }
+
+            override fun onLaunchAnimationEnd() {
+                isLaunchingActivity = false
+                updateAlpha()
+            }
+
+            override fun onLaunchAnimationProgress(linearProgress: Float) {
+                activityLaunchProgress = linearProgress
+                updateAlpha()
+            }
+        }
+
+    private val statusBarKeyguardViewManagerCallback: KeyguardViewManagerCallback =
+        object : KeyguardViewManagerCallback {
+            override fun onQSExpansionChanged(qsExpansion: Float) {
+                this@UdfpsKeyguardViewController.qsExpansion = qsExpansion
+                updateAlpha()
+                updatePauseAuth()
+            }
+
+            /**
+             * Forward touches to the UdfpsController. This allows the touch to start from outside
+             * the sensor area and then slide their finger into the sensor area.
+             */
+            override fun onTouch(event: MotionEvent) {
+                // Don't forward touches if the shade has already started expanding.
+                if (transitionToFullShadeProgress != 0f) {
+                    return
+                }
+                udfpsController.onTouch(event)
+            }
+        }
+
+    private val alternateAuthInterceptor: AlternateAuthInterceptor =
+        object : AlternateAuthInterceptor {
+            override fun showAlternateAuthBouncer(): Boolean {
+                return showUdfpsBouncer(true)
+            }
+
+            override fun hideAlternateAuthBouncer(): Boolean {
+                return showUdfpsBouncer(false)
+            }
+
+            override fun isShowingAlternateAuthBouncer(): Boolean {
+                return showingUdfpsBouncer
+            }
+
+            override fun requestUdfps(request: Boolean, color: Int) {
+                udfpsRequested = request
+                view.requestUdfps(request, color)
+                updateAlpha()
+                updatePauseAuth()
+            }
+
+            override fun dump(pw: PrintWriter) {
+                pw.println(tag)
+            }
+        }
+
+    override val tag: String
+        get() = TAG
+
+    override fun onInit() {
+        super.onInit()
+        keyguardViewManager.setAlternateAuthInterceptor(alternateAuthInterceptor)
+    }
+
+    init {
+        if (isModernBouncerEnabled) {
+            view.repeatWhenAttached {
+                // repeatOnLifecycle CREATED (as opposed to STARTED) because the Bouncer expansion
+                // can make the view not visible; and we still want to listen for events
+                // that may make the view visible again.
+                repeatOnLifecycle(Lifecycle.State.CREATED) { listenForBouncerExpansion(this) }
+            }
+        }
+    }
+
+    @VisibleForTesting
+    internal suspend fun listenForBouncerExpansion(scope: CoroutineScope): Job {
+        return scope.launch {
+            bouncerInteractor.bouncerExpansion.collect { bouncerExpansion: Float ->
+                inputBouncerExpansion = bouncerExpansion
+                updateAlpha()
+                updatePauseAuth()
+            }
+        }
+    }
+
+    public override fun onViewAttached() {
+        super.onViewAttached()
+        val dozeAmount = statusBarStateController.dozeAmount
+        lastDozeAmount = dozeAmount
+        stateListener.onDozeAmountChanged(dozeAmount, dozeAmount)
+        statusBarStateController.addCallback(stateListener)
+        udfpsRequested = false
+        launchTransitionFadingAway = keyguardStateController.isLaunchTransitionFadingAway
+        keyguardStateController.addCallback(keyguardStateControllerCallback)
+        statusBarState = statusBarStateController.state
+        qsExpansion = keyguardViewManager.qsExpansion
+        keyguardViewManager.addCallback(statusBarKeyguardViewManagerCallback)
+        if (!isModernBouncerEnabled) {
+            val bouncer = keyguardViewManager.bouncer
+            bouncer?.expansion?.let {
+                bouncerExpansionCallback.onExpansionChanged(it)
+                bouncer.addBouncerExpansionCallback(bouncerExpansionCallback)
+            }
+            updateBouncerHiddenAmount()
+        }
+        configurationController.addCallback(configurationListener)
+        shadeExpansionStateManager.addExpansionListener(shadeExpansionListener)
+        updateScaleFactor()
+        view.updatePadding()
+        updateAlpha()
+        updatePauseAuth()
+        keyguardViewManager.setAlternateAuthInterceptor(alternateAuthInterceptor)
+        lockScreenShadeTransitionController.udfpsKeyguardViewController = this
+        activityLaunchAnimator.addListener(activityLaunchAnimatorListener)
+    }
+
+    override fun onViewDetached() {
+        super.onViewDetached()
+        faceDetectRunning = false
+        keyguardStateController.removeCallback(keyguardStateControllerCallback)
+        statusBarStateController.removeCallback(stateListener)
+        keyguardViewManager.removeAlternateAuthInterceptor(alternateAuthInterceptor)
+        keyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false)
+        configurationController.removeCallback(configurationListener)
+        shadeExpansionStateManager.removeExpansionListener(shadeExpansionListener)
+        if (lockScreenShadeTransitionController.udfpsKeyguardViewController === this) {
+            lockScreenShadeTransitionController.udfpsKeyguardViewController = null
+        }
+        activityLaunchAnimator.removeListener(activityLaunchAnimatorListener)
+        keyguardViewManager.removeCallback(statusBarKeyguardViewManagerCallback)
+        if (!isModernBouncerEnabled) {
+            keyguardViewManager.bouncer?.removeBouncerExpansionCallback(bouncerExpansionCallback)
+        }
+    }
+
+    override fun dump(pw: PrintWriter, args: Array<String>) {
+        super.dump(pw, args)
+        pw.println("isModernBouncerEnabled=$isModernBouncerEnabled")
+        pw.println("showingUdfpsAltBouncer=$showingUdfpsBouncer")
+        pw.println("faceDetectRunning=$faceDetectRunning")
+        pw.println("statusBarState=" + StatusBarState.toString(statusBarState))
+        pw.println("transitionToFullShadeProgress=$transitionToFullShadeProgress")
+        pw.println("qsExpansion=$qsExpansion")
+        pw.println("panelExpansionFraction=$panelExpansionFraction")
+        pw.println("unpausedAlpha=" + view.unpausedAlpha)
+        pw.println("udfpsRequestedByApp=$udfpsRequested")
+        pw.println("launchTransitionFadingAway=$launchTransitionFadingAway")
+        pw.println("lastDozeAmount=$lastDozeAmount")
+        if (isModernBouncerEnabled) {
+            pw.println("inputBouncerExpansion=$inputBouncerExpansion")
+        } else {
+            pw.println("inputBouncerHiddenAmount=$inputBouncerHiddenAmount")
+        }
+        view.dump(pw)
+    }
+
+    /**
+     * Overrides non-bouncer show logic in shouldPauseAuth to still show icon.
+     * @return whether the udfpsBouncer has been newly shown or hidden
+     */
+    private fun showUdfpsBouncer(show: Boolean): Boolean {
+        if (showingUdfpsBouncer == show) {
+            return false
+        }
+        val udfpsAffordanceWasNotShowing = shouldPauseAuth()
+        showingUdfpsBouncer = show
+        if (showingUdfpsBouncer) {
+            lastUdfpsBouncerShowTime = systemClock.uptimeMillis()
+        }
+        if (showingUdfpsBouncer) {
+            if (udfpsAffordanceWasNotShowing) {
+                view.animateInUdfpsBouncer(null)
+            }
+            if (keyguardStateController.isOccluded) {
+                keyguardUpdateMonitor.requestFaceAuthOnOccludingApp(true)
+            }
+            view.announceForAccessibility(
+                view.context.getString(R.string.accessibility_fingerprint_bouncer)
+            )
+        } else {
+            keyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false)
+        }
+        updateBouncerHiddenAmount()
+        updateAlpha()
+        updatePauseAuth()
+        return true
+    }
+
+    /**
+     * Returns true if the fingerprint manager is running but we want to temporarily pause
+     * authentication. On the keyguard, we may want to show udfps when the shade is expanded, so
+     * this can be overridden with the showBouncer method.
+     */
+    override fun shouldPauseAuth(): Boolean {
+        if (showingUdfpsBouncer) {
+            return false
+        }
+        if (
+            udfpsRequested &&
+                !notificationShadeVisible &&
+                !isInputBouncerFullyVisible() &&
+                keyguardStateController.isShowing
+        ) {
+            return false
+        }
+        if (launchTransitionFadingAway) {
+            return true
+        }
+
+        // Only pause auth if we're not on the keyguard AND we're not transitioning to doze
+        // (ie: dozeAmount = 0f). For the UnlockedScreenOffAnimation, the statusBarState is
+        // delayed. However, we still animate in the UDFPS affordance with the
+        // mUnlockedScreenOffDozeAnimator.
+        if (statusBarState != StatusBarState.KEYGUARD && lastDozeAmount == 0f) {
+            return true
+        }
+        if (isBouncerExpansionGreaterThan(.5f)) {
+            return true
+        }
+        return view.unpausedAlpha < 255 * .1
+    }
+
+    fun isBouncerExpansionGreaterThan(bouncerExpansionThreshold: Float): Boolean {
+        return if (isModernBouncerEnabled) {
+            inputBouncerExpansion >= bouncerExpansionThreshold
+        } else {
+            inputBouncerHiddenAmount < bouncerExpansionThreshold
+        }
+    }
+
+    fun isInputBouncerFullyVisible(): Boolean {
+        return if (isModernBouncerEnabled) {
+            inputBouncerExpansion == 1f
+        } else {
+            keyguardViewManager.isBouncerShowing && !keyguardViewManager.isShowingAlternateAuth
+        }
+    }
+
+    override fun listenForTouchesOutsideView(): Boolean {
+        return true
+    }
+
+    override fun onTouchOutsideView() {
+        maybeShowInputBouncer()
+    }
+
+    /**
+     * If we were previously showing the udfps bouncer, hide it and instead show the regular
+     * (pin/pattern/password) bouncer.
+     *
+     * Does nothing if we weren't previously showing the UDFPS bouncer.
+     */
+    private fun maybeShowInputBouncer() {
+        if (showingUdfpsBouncer && hasUdfpsBouncerShownWithMinTime()) {
+            keyguardViewManager.showBouncer(true)
+        }
+    }
+
+    /**
+     * Whether the udfps bouncer has shown for at least 200ms before allowing touches outside of the
+     * udfps icon area to dismiss the udfps bouncer and show the pin/pattern/password bouncer.
+     */
+    private fun hasUdfpsBouncerShownWithMinTime(): Boolean {
+        return systemClock.uptimeMillis() - lastUdfpsBouncerShowTime > 200
+    }
+
+    /**
+     * Set the progress we're currently transitioning to the full shade. 0.0f means we're not
+     * transitioning yet, while 1.0f means we've fully dragged down. For example, start swiping down
+     * to expand the notification shade from the empty space in the middle of the lock screen.
+     */
+    fun setTransitionToFullShadeProgress(progress: Float) {
+        transitionToFullShadeProgress = progress
+        updateAlpha()
+    }
+
+    /**
+     * Update alpha for the UDFPS lock screen affordance. The AoD UDFPS visual affordance's alpha is
+     * based on the doze amount.
+     */
+    override fun updateAlpha() {
+        // Fade icon on transitions to showing the status bar or bouncer, but if mUdfpsRequested,
+        // then the keyguard is occluded by some application - so instead use the input bouncer
+        // hidden amount to determine the fade.
+        val expansion = if (udfpsRequested) getInputBouncerHiddenAmt() else panelExpansionFraction
+        var alpha: Int =
+            if (showingUdfpsBouncer) 255
+            else MathUtils.constrain(MathUtils.map(.5f, .9f, 0f, 255f, expansion), 0f, 255f).toInt()
+        if (!showingUdfpsBouncer) {
+            // swipe from top of the lockscreen to expand full QS:
+            alpha =
+                (alpha * (1.0f - Interpolators.EMPHASIZED_DECELERATE.getInterpolation(qsExpansion)))
+                    .toInt()
+
+            // swipe from the middle (empty space) of lockscreen to expand the notification shade:
+            alpha = (alpha * (1.0f - transitionToFullShadeProgress)).toInt()
+
+            // Fade out the icon if we are animating an activity launch over the lockscreen and the
+            // activity didn't request the UDFPS.
+            if (isLaunchingActivity && !udfpsRequested) {
+                alpha = (alpha * (1.0f - activityLaunchProgress)).toInt()
+            }
+
+            // Fade out alpha when a dialog is shown
+            // Fade in alpha when a dialog is hidden
+            alpha = (alpha * view.dialogSuggestedAlpha).toInt()
+        }
+        view.unpausedAlpha = alpha
+    }
+
+    private fun getInputBouncerHiddenAmt(): Float {
+        return if (isModernBouncerEnabled) {
+            1f - inputBouncerExpansion
+        } else {
+            inputBouncerHiddenAmount
+        }
+    }
+
+    /** Update the scale factor based on the device's resolution. */
+    private fun updateScaleFactor() {
+        udfpsController.mOverlayParams?.scaleFactor?.let { view.setScaleFactor(it) }
+    }
+
+    private fun updateBouncerHiddenAmount() {
+        if (isModernBouncerEnabled) {
+            return
+        }
+        val altBouncerShowing = keyguardViewManager.isShowingAlternateAuth
+        if (altBouncerShowing || !keyguardViewManager.bouncerIsOrWillBeShowing()) {
+            inputBouncerHiddenAmount = 1f
+        } else if (keyguardViewManager.isBouncerShowing) {
+            // input bouncer is fully showing
+            inputBouncerHiddenAmount = 0f
+        }
+    }
+
+    companion object {
+        const val TAG = "UdfpsKeyguardViewController"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlay.kt
index 6e78f3d..142642a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlay.kt
@@ -19,26 +19,40 @@
 import android.annotation.SuppressLint
 import android.content.Context
 import android.graphics.PixelFormat
+import android.graphics.Point
 import android.graphics.Rect
+import android.hardware.biometrics.BiometricOverlayConstants
 import android.hardware.fingerprint.FingerprintManager
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
 import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback
+import android.hardware.fingerprint.IUdfpsOverlay
 import android.os.Handler
+import android.provider.Settings
 import android.view.MotionEvent
-import android.view.View
 import android.view.WindowManager
 import android.view.WindowManager.LayoutParams.INPUT_FEATURE_SPY
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.util.concurrency.DelayableExecutor
 import com.android.systemui.util.concurrency.Execution
-import java.util.*
+import java.util.Optional
 import java.util.concurrent.Executor
 import javax.inject.Inject
+import kotlin.math.cos
+import kotlin.math.pow
+import kotlin.math.sin
 
 private const val TAG = "UdfpsOverlay"
 
+const val SETTING_OVERLAY_DEBUG = "udfps_overlay_debug"
+
+// Number of sensor points needed inside ellipse for good overlap
+private const val NEEDED_POINTS = 2
+
 @SuppressLint("ClickableViewAccessibility")
 @SysUISingleton
 class UdfpsOverlay
@@ -51,10 +65,11 @@
     private val handler: Handler,
     private val biometricExecutor: Executor,
     private val alternateTouchProvider: Optional<AlternateUdfpsTouchProvider>,
-    private val fgExecutor: DelayableExecutor,
+    @Main private val fgExecutor: DelayableExecutor,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     private val authController: AuthController,
-    private val udfpsLogger: UdfpsLogger
+    private val udfpsLogger: UdfpsLogger,
+    private var featureFlags: FeatureFlags
 ) : CoreStartable {
 
     /** The view, when [isShowing], or null. */
@@ -64,7 +79,11 @@
     private var requestId: Long = 0
     private var onFingerDown = false
     val size = windowManager.maximumWindowMetrics.bounds
+
     val udfpsProps: MutableList<FingerprintSensorPropertiesInternal> = mutableListOf()
+    var points: Array<Point> = emptyArray()
+    var processedMotionEvent = false
+    var isShowing = false
 
     private var params: UdfpsOverlayParams = UdfpsOverlayParams()
 
@@ -87,41 +106,140 @@
                 inputFeatures = INPUT_FEATURE_SPY
             }
 
-    fun onTouch(v: View, event: MotionEvent): Boolean {
-        val view = v as UdfpsOverlayView
+    fun onTouch(event: MotionEvent): Boolean {
+        val view = overlayView!!
 
         return when (event.action) {
             MotionEvent.ACTION_DOWN,
             MotionEvent.ACTION_MOVE -> {
                 onFingerDown = true
                 if (!view.isDisplayConfigured && alternateTouchProvider.isPresent) {
-                    biometricExecutor.execute {
-                        alternateTouchProvider
-                            .get()
-                            .onPointerDown(
-                                requestId,
-                                event.x.toInt(),
-                                event.y.toInt(),
-                                event.touchMinor,
-                                event.touchMajor
-                            )
-                    }
-                    fgExecutor.execute {
-                        if (keyguardUpdateMonitor.isFingerprintDetectionRunning) {
-                            keyguardUpdateMonitor.onUdfpsPointerDown(requestId.toInt())
+                    view.processMotionEvent(event)
+
+                    val goodOverlap =
+                        if (featureFlags.isEnabled(Flags.NEW_ELLIPSE_DETECTION)) {
+                            isGoodEllipseOverlap(event)
+                        } else {
+                            isGoodCentroidOverlap(event)
+                        }
+
+                    if (!processedMotionEvent && goodOverlap) {
+                        biometricExecutor.execute {
+                            alternateTouchProvider
+                                .get()
+                                .onPointerDown(
+                                    requestId,
+                                    event.rawX.toInt(),
+                                    event.rawY.toInt(),
+                                    event.touchMinor,
+                                    event.touchMajor
+                                )
+                        }
+                        fgExecutor.execute {
+                            if (keyguardUpdateMonitor.isFingerprintDetectionRunning) {
+                                keyguardUpdateMonitor.onUdfpsPointerDown(requestId.toInt())
+                            }
+
+                            view.configureDisplay {
+                                biometricExecutor.execute {
+                                    alternateTouchProvider.get().onUiReady()
+                                }
+                            }
+
+                            processedMotionEvent = true
                         }
                     }
 
-                    view.configureDisplay {
-                        biometricExecutor.execute { alternateTouchProvider.get().onUiReady() }
-                    }
+                    view.invalidate()
                 }
-
                 true
             }
             MotionEvent.ACTION_UP,
             MotionEvent.ACTION_CANCEL -> {
-                if (onFingerDown && alternateTouchProvider.isPresent) {
+                if (processedMotionEvent && alternateTouchProvider.isPresent) {
+                    biometricExecutor.execute {
+                        alternateTouchProvider.get().onPointerUp(requestId)
+                    }
+                    fgExecutor.execute {
+                        if (keyguardUpdateMonitor.isFingerprintDetectionRunning) {
+                            keyguardUpdateMonitor.onUdfpsPointerUp(requestId.toInt())
+                        }
+                    }
+
+                    processedMotionEvent = false
+                }
+
+                if (view.isDisplayConfigured) {
+                    view.unconfigureDisplay()
+                }
+
+                view.invalidate()
+                true
+            }
+            else -> false
+        }
+    }
+
+    fun isGoodEllipseOverlap(event: MotionEvent): Boolean {
+        return points.count { checkPoint(event, it) } >= NEEDED_POINTS
+    }
+
+    fun isGoodCentroidOverlap(event: MotionEvent): Boolean {
+        return params.sensorBounds.contains(event.rawX.toInt(), event.rawY.toInt())
+    }
+
+    fun checkPoint(event: MotionEvent, point: Point): Boolean {
+        // Calculate if sensor point is within ellipse
+        // Formula: ((cos(o)(xE - xS) + sin(o)(yE - yS))^2 / a^2) + ((sin(o)(xE - xS) + cos(o)(yE -
+        // yS))^2 / b^2) <= 1
+        val a: Float = cos(event.orientation) * (point.x - event.rawX)
+        val b: Float = sin(event.orientation) * (point.y - event.rawY)
+        val c: Float = sin(event.orientation) * (point.x - event.rawX)
+        val d: Float = cos(event.orientation) * (point.y - event.rawY)
+        val result =
+            (a + b).pow(2) / (event.touchMinor / 2).pow(2) +
+                (c - d).pow(2) / (event.touchMajor / 2).pow(2)
+
+        return result <= 1
+    }
+
+    fun show(requestId: Long) {
+        if (!featureFlags.isEnabled(Flags.NEW_UDFPS_OVERLAY)) {
+            return
+        }
+
+        this.requestId = requestId
+        fgExecutor.execute {
+            if (overlayView == null && alternateTouchProvider.isPresent) {
+                UdfpsOverlayView(context, null).let {
+                    it.overlayParams = params
+                    it.setUdfpsDisplayMode(
+                        UdfpsDisplayMode(context, execution, authController, udfpsLogger)
+                    )
+                    it.setOnTouchListener { _, event -> onTouch(event) }
+                    it.sensorPoints = points
+                    it.debugOverlay =
+                        Settings.Global.getInt(
+                            context.contentResolver,
+                            SETTING_OVERLAY_DEBUG,
+                            0 /* def */
+                        ) != 0
+                    overlayView = it
+                }
+                windowManager.addView(overlayView, coreLayoutParams)
+                isShowing = true
+            }
+        }
+    }
+
+    fun hide() {
+        if (!featureFlags.isEnabled(Flags.NEW_UDFPS_OVERLAY)) {
+            return
+        }
+
+        fgExecutor.execute {
+            if (overlayView != null && isShowing && alternateTouchProvider.isPresent) {
+                if (processedMotionEvent) {
                     biometricExecutor.execute {
                         alternateTouchProvider.get().onPointerUp(requestId)
                     }
@@ -131,44 +249,23 @@
                         }
                     }
                 }
-                onFingerDown = false
-                if (view.isDisplayConfigured) {
-                    view.unconfigureDisplay()
+
+                if (overlayView!!.isDisplayConfigured) {
+                    overlayView!!.unconfigureDisplay()
                 }
 
-                true
+                overlayView?.apply {
+                    windowManager.removeView(this)
+                    setOnTouchListener(null)
+                }
+
+                isShowing = false
+                overlayView = null
+                processedMotionEvent = false
             }
-            else -> false
         }
     }
 
-    fun show(requestId: Long): Boolean {
-        this.requestId = requestId
-        if (overlayView == null && alternateTouchProvider.isPresent) {
-            UdfpsOverlayView(context, null).let {
-                it.overlayParams = params
-                it.setUdfpsDisplayMode(
-                    UdfpsDisplayMode(context, execution, authController, udfpsLogger)
-                )
-                it.setOnTouchListener { v, event -> onTouch(v, event) }
-                overlayView = it
-            }
-            windowManager.addView(overlayView, coreLayoutParams)
-            return true
-        }
-
-        return false
-    }
-
-    fun hide() {
-        overlayView?.apply {
-            windowManager.removeView(this)
-            setOnTouchListener(null)
-        }
-
-        overlayView = null
-    }
-
     @Override
     override fun start() {
         fingerprintManager?.addAuthenticatorsRegisteredCallback(
@@ -180,6 +277,18 @@
                 }
             }
         )
+
+        fingerprintManager?.setUdfpsOverlay(
+            object : IUdfpsOverlay.Stub() {
+                override fun show(
+                    requestId: Long,
+                    sensorId: Int,
+                    @BiometricOverlayConstants.ShowReason reason: Int
+                ) = show(requestId)
+
+                override fun hide(sensorId: Int) = hide()
+            }
+        )
     }
 
     private fun handleAllFingerprintAuthenticatorsRegistered(
@@ -201,6 +310,24 @@
                     naturalDisplayHeight = size.height(),
                     scaleFactor = 1f
                 )
+
+            val sensorX = params.sensorBounds.centerX()
+            val sensorY = params.sensorBounds.centerY()
+            val cornerOffset: Int = params.sensorBounds.width() / 4
+            val sideOffset: Int = params.sensorBounds.width() / 3
+
+            points =
+                arrayOf(
+                    Point(sensorX - cornerOffset, sensorY - cornerOffset),
+                    Point(sensorX, sensorY - sideOffset),
+                    Point(sensorX + cornerOffset, sensorY - cornerOffset),
+                    Point(sensorX - sideOffset, sensorY),
+                    Point(sensorX, sensorY),
+                    Point(sensorX + sideOffset, sensorY),
+                    Point(sensorX - cornerOffset, sensorY + cornerOffset),
+                    Point(sensorX, sensorY + sideOffset),
+                    Point(sensorX + cornerOffset, sensorY + cornerOffset)
+                )
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlayView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlayView.kt
index d371332..4e6a06b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlayView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsOverlayView.kt
@@ -20,26 +20,41 @@
 import android.graphics.Canvas
 import android.graphics.Color
 import android.graphics.Paint
+import android.graphics.Point
 import android.graphics.RectF
 import android.util.AttributeSet
+import android.view.MotionEvent
 import android.widget.FrameLayout
 
 private const val TAG = "UdfpsOverlayView"
+private const val POINT_SIZE = 10f
 
 class UdfpsOverlayView(context: Context, attrs: AttributeSet?) : FrameLayout(context, attrs) {
-
-    private val sensorRect = RectF()
     var overlayParams = UdfpsOverlayParams()
     private var mUdfpsDisplayMode: UdfpsDisplayMode? = null
 
+    var debugOverlay = false
+
     var overlayPaint = Paint()
     var sensorPaint = Paint()
+    var touchPaint = Paint()
+    var pointPaint = Paint()
     val centerPaint = Paint()
 
+    var oval = RectF()
+
     /** True after the call to [configureDisplay] and before the call to [unconfigureDisplay]. */
     var isDisplayConfigured: Boolean = false
         private set
 
+    var touchX: Float = 0f
+    var touchY: Float = 0f
+    var touchMinor: Float = 0f
+    var touchMajor: Float = 0f
+    var touchOrientation: Double = 0.0
+
+    var sensorPoints: Array<Point>? = null
+
     init {
         this.setWillNotDraw(false)
     }
@@ -47,24 +62,60 @@
     override fun onAttachedToWindow() {
         super.onAttachedToWindow()
 
-        overlayPaint.color = Color.argb(120, 255, 0, 0)
+        overlayPaint.color = Color.argb(100, 255, 0, 0)
         overlayPaint.style = Paint.Style.FILL
 
+        touchPaint.color = Color.argb(200, 255, 255, 255)
+        touchPaint.style = Paint.Style.FILL
+
         sensorPaint.color = Color.argb(150, 134, 204, 255)
         sensorPaint.style = Paint.Style.FILL
+
+        pointPaint.color = Color.WHITE
+        pointPaint.style = Paint.Style.FILL
     }
 
     override fun onDraw(canvas: Canvas) {
         super.onDraw(canvas)
 
-        canvas.drawRect(overlayParams.overlayBounds, overlayPaint)
-        canvas.drawRect(overlayParams.sensorBounds, sensorPaint)
+        if (debugOverlay) {
+            // Draw overlay and sensor bounds
+            canvas.drawRect(overlayParams.overlayBounds, overlayPaint)
+            canvas.drawRect(overlayParams.sensorBounds, sensorPaint)
+        }
+
+        // Draw sensor circle
         canvas.drawCircle(
             overlayParams.sensorBounds.exactCenterX(),
             overlayParams.sensorBounds.exactCenterY(),
             overlayParams.sensorBounds.width().toFloat() / 2,
             centerPaint
         )
+
+        if (debugOverlay) {
+            // Draw Points
+            sensorPoints?.forEach {
+                canvas.drawCircle(it.x.toFloat(), it.y.toFloat(), POINT_SIZE, pointPaint)
+            }
+
+            // Draw touch oval
+            canvas.save()
+            canvas.rotate(Math.toDegrees(touchOrientation).toFloat(), touchX, touchY)
+
+            oval.setEmpty()
+            oval.set(
+                touchX - touchMinor / 2,
+                touchY + touchMajor / 2,
+                touchX + touchMinor / 2,
+                touchY - touchMajor / 2
+            )
+
+            canvas.drawOval(oval, touchPaint)
+
+            // Draw center point
+            canvas.drawCircle(touchX, touchY, POINT_SIZE, centerPaint)
+            canvas.restore()
+        }
     }
 
     fun setUdfpsDisplayMode(udfpsDisplayMode: UdfpsDisplayMode?) {
@@ -80,4 +131,12 @@
         isDisplayConfigured = false
         mUdfpsDisplayMode?.disable(null /* onDisabled */)
     }
+
+    fun processMotionEvent(event: MotionEvent) {
+        touchX = event.rawX
+        touchY = event.rawY
+        touchMinor = event.touchMinor
+        touchMajor = event.touchMajor
+        touchOrientation = event.orientation.toDouble()
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt
index 75640b7..f48cfd3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.biometrics
 
 import android.content.Context
+import android.graphics.Rect
 import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_BP
 import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
 import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_OTHER
@@ -27,6 +28,11 @@
 import android.hardware.fingerprint.IUdfpsOverlayControllerCallback
 import android.util.Log
 import android.view.LayoutInflater
+import android.view.MotionEvent
+import android.view.MotionEvent.ACTION_DOWN
+import android.view.MotionEvent.ACTION_MOVE
+import android.view.MotionEvent.ACTION_UP
+import com.android.internal.annotations.VisibleForTesting
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.statusbar.commandline.Command
 import com.android.systemui.statusbar.commandline.CommandRegistry
@@ -36,6 +42,8 @@
 private const val TAG = "UdfpsShell"
 private const val REQUEST_ID = 2L
 private const val SENSOR_ID = 0
+private const val MINOR = 10F
+private const val MAJOR = 10F
 
 /**
  * Used to show and hide the UDFPS overlay with statusbar commands.
@@ -62,12 +70,17 @@
         if (args.size == 1 && args[0] == "hide") {
             hideOverlay()
         } else if (args.size == 2 && args[0] == "udfpsOverlay" && args[1] == "show") {
-            hideOverlay()
             showUdfpsOverlay()
         } else if (args.size == 2 && args[0] == "udfpsOverlay" && args[1] == "hide") {
             hideUdfpsOverlay()
         } else if (args.size == 2 && args[0] == "show") {
             showOverlay(getEnrollmentReason(args[1]))
+        } else if (args.size == 1 && args[0] == "onUiReady") {
+            onUiReady()
+        } else if (args.size == 1 && args[0] == "simFingerDown") {
+            simFingerDown()
+        } else if (args.size == 1 && args[0] == "simFingerUp") {
+            simFingerUp()
         } else {
             invalidCommand(pw)
         }
@@ -81,6 +94,11 @@
                             "auth-keyguard, auth-other, auth-settings]")
         pw.println("    -> reason otherwise defaults to unknown")
         pw.println("  - hide")
+        pw.println("  - onUiReady")
+        pw.println("  - simFingerDown")
+        pw.println("    -> Simulates onFingerDown on sensor")
+        pw.println("  - simFingerUp")
+        pw.println("    -> Simulates onFingerUp on sensor")
     }
 
     private fun invalidCommand(pw: PrintWriter) {
@@ -126,4 +144,54 @@
     private fun hideOverlay() {
         udfpsOverlayController?.hideUdfpsOverlay(SENSOR_ID)
     }
+
+
+    @VisibleForTesting
+    fun onUiReady() {
+        udfpsOverlayController?.debugOnUiReady(REQUEST_ID, SENSOR_ID)
+    }
+
+    @VisibleForTesting
+    fun simFingerDown() {
+        val sensorBounds: Rect = udfpsOverlayController!!.sensorBounds
+
+        val downEvent: MotionEvent? = obtainMotionEvent(ACTION_DOWN, sensorBounds.exactCenterX(),
+                sensorBounds.exactCenterY(), MINOR, MAJOR)
+        udfpsOverlayController?.debugOnTouch(REQUEST_ID, downEvent)
+
+        val moveEvent: MotionEvent? = obtainMotionEvent(ACTION_MOVE, sensorBounds.exactCenterX(),
+                sensorBounds.exactCenterY(), MINOR, MAJOR)
+        udfpsOverlayController?.debugOnTouch(REQUEST_ID, moveEvent)
+
+        downEvent?.recycle()
+        moveEvent?.recycle()
+    }
+
+    @VisibleForTesting
+    fun simFingerUp() {
+        val sensorBounds: Rect = udfpsOverlayController!!.sensorBounds
+
+        val upEvent: MotionEvent? = obtainMotionEvent(ACTION_UP, sensorBounds.exactCenterX(),
+                sensorBounds.exactCenterY(), MINOR, MAJOR)
+        udfpsOverlayController?.debugOnTouch(REQUEST_ID, upEvent)
+        upEvent?.recycle()
+    }
+
+    private fun obtainMotionEvent(
+            action: Int,
+            x: Float,
+            y: Float,
+            minor: Float,
+            major: Float
+    ): MotionEvent? {
+        val pp = MotionEvent.PointerProperties()
+        pp.id = 1
+        val pc = MotionEvent.PointerCoords()
+        pc.x = x
+        pc.y = y
+        pc.touchMinor = minor
+        pc.touchMajor = major
+        return MotionEvent.obtain(0, 0, action, 1, arrayOf(pp), arrayOf(pc),
+                0, 0, 1f, 1f, 0, 0, 0, 0)
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
index 22dc94a..08c7c0f 100644
--- a/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
+++ b/packages/SystemUI/src/com/android/systemui/broadcast/UserBroadcastDispatcher.kt
@@ -21,6 +21,7 @@
 import android.content.Context
 import android.os.Handler
 import android.os.Looper
+import android.os.Trace
 import android.os.UserHandle
 import android.util.ArrayMap
 import android.util.ArraySet
@@ -126,6 +127,10 @@
                 action,
                 userId,
                 {
+                    if (Trace.isEnabled()) {
+                        Trace.traceBegin(
+                                Trace.TRACE_TAG_APP, "registerReceiver act=$action user=$userId")
+                    }
                     context.registerReceiverAsUser(
                             this,
                             UserHandle.of(userId),
@@ -134,11 +139,18 @@
                             workerHandler,
                             flags
                     )
+                    Trace.endSection()
                     logger.logContextReceiverRegistered(userId, flags, it)
                 },
                 {
                     try {
+                        if (Trace.isEnabled()) {
+                            Trace.traceBegin(
+                                    Trace.TRACE_TAG_APP,
+                                    "unregisterReceiver act=$action user=$userId")
+                        }
                         context.unregisterReceiver(this)
+                        Trace.endSection()
                         logger.logContextReceiverUnregistered(userId, action)
                     } catch (e: IllegalArgumentException) {
                         Log.e(TAG, "Trying to unregister unregistered receiver for user $userId, " +
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
index dec3d6b..616e49c 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/charging/WiredChargingRippleController.kt
@@ -149,7 +149,7 @@
     }
 
     fun startRipple() {
-        if (rippleView.rippleInProgress || rippleView.parent != null) {
+        if (rippleView.rippleInProgress() || rippleView.parent != null) {
             // Skip if ripple is still playing, or not playing but already added the parent
             // (which might happen just before the animation starts or right after
             // the animation ends.)
diff --git a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
index c0cc6b4..1455699 100644
--- a/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/charging/WirelessChargingLayout.java
@@ -33,9 +33,9 @@
 import com.android.settingslib.Utils;
 import com.android.systemui.R;
 import com.android.systemui.animation.Interpolators;
+import com.android.systemui.ripple.RippleAnimationConfig;
 import com.android.systemui.ripple.RippleShader.RippleShape;
 import com.android.systemui.ripple.RippleView;
-import com.android.systemui.ripple.RippleViewKt;
 
 import java.text.NumberFormat;
 
@@ -150,7 +150,7 @@
             mRippleView.setColor(color, 28);
         } else {
             mRippleView.setDuration(CIRCLE_RIPPLE_ANIMATION_DURATION);
-            mRippleView.setColor(color, RippleViewKt.RIPPLE_DEFAULT_ALPHA);
+            mRippleView.setColor(color, RippleAnimationConfig.RIPPLE_DEFAULT_ALPHA);
         }
 
         OnAttachStateChangeListener listener = new OnAttachStateChangeListener() {
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
index 500f280..2245d84 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/BrightLineFalsingManager.java
@@ -337,7 +337,8 @@
                 || mTestHarness
                 || mDataProvider.isJustUnlockedWithFace()
                 || mDataProvider.isDocked()
-                || mAccessibilityManager.isTouchExplorationEnabled();
+                || mAccessibilityManager.isTouchExplorationEnabled()
+                || mDataProvider.isA11yAction();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingA11yDelegate.kt b/packages/SystemUI/src/com/android/systemui/classifier/FalsingA11yDelegate.kt
new file mode 100644
index 0000000..63d57cc
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingA11yDelegate.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2022 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.classifier
+
+import android.os.Bundle
+import android.view.View
+import android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK
+import javax.inject.Inject
+
+/**
+ * Class that injects an artificial tap into the falsing collector.
+ *
+ * This is used for views that can be interacted with by A11y services and have falsing checks, as
+ * the gestures made by the A11y framework do not propagate motion events down the view hierarchy.
+ */
+class FalsingA11yDelegate @Inject constructor(private val falsingCollector: FalsingCollector) :
+    View.AccessibilityDelegate() {
+    override fun performAccessibilityAction(host: View?, action: Int, args: Bundle?): Boolean {
+        if (action == ACTION_CLICK) {
+            falsingCollector.onA11yAction()
+        }
+        return super.performAccessibilityAction(host, action, args)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java
index 858bac3..6670108 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollector.java
@@ -132,5 +132,8 @@
 
     /** */
     void updateFalseConfidence(FalsingClassifier.Result result);
+
+    /** Indicates an a11y action was made. */
+    void onA11yAction();
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java
index 0b7d6ab..cc25368 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorFake.java
@@ -157,4 +157,8 @@
     @Override
     public void updateFalseConfidence(FalsingClassifier.Result result) {
     }
+
+    @Override
+    public void onA11yAction() {
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
index da3d293..8bdef13 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingCollectorImpl.java
@@ -375,6 +375,15 @@
         mHistoryTracker.addResults(Collections.singleton(result), mSystemClock.uptimeMillis());
     }
 
+    @Override
+    public void onA11yAction() {
+        if (mPendingDownEvent != null) {
+            mPendingDownEvent.recycle();
+            mPendingDownEvent = null;
+        }
+        mFalsingDataProvider.onA11yAction();
+    }
+
     private boolean shouldSessionBeActive() {
         return mScreenOn && (mState == StatusBarState.KEYGUARD) && !mShowingAod;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
index 3991a35..09ebeea 100644
--- a/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
+++ b/packages/SystemUI/src/com/android/systemui/classifier/FalsingDataProvider.java
@@ -59,6 +59,7 @@
     private MotionEvent mFirstRecentMotionEvent;
     private MotionEvent mLastMotionEvent;
     private boolean mJustUnlockedWithFace;
+    private boolean mA11YAction;
 
     @Inject
     public FalsingDataProvider(
@@ -124,6 +125,7 @@
             mPriorMotionEvents = mRecentMotionEvents;
             mRecentMotionEvents = new TimeLimitedMotionEventBuffer(MOTION_EVENT_AGE_MS);
         }
+        mA11YAction = false;
     }
 
     /** Returns screen width in pixels. */
@@ -334,6 +336,17 @@
         mGestureFinalizedListeners.remove(listener);
     }
 
+    /** Return whether last gesture was an A11y action. */
+    public boolean isA11yAction() {
+        return mA11YAction;
+    }
+
+    /** Set whether last gesture was an A11y action. */
+    public void onA11yAction() {
+        completePriorGesture();
+        this.mA11YAction = true;
+    }
+
     void onSessionStarted() {
         mSessionListeners.forEach(SessionListener::onSessionStarted);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index 9f338d1..c853671 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -31,6 +31,7 @@
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SWIPE_DISMISSED;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TAP_OUTSIDE;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TIMED_OUT;
+import static com.android.systemui.flags.Flags.CLIPBOARD_REMOTE_BEHAVIOR;
 
 import static java.util.Objects.requireNonNull;
 
@@ -73,6 +74,7 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.broadcast.BroadcastSender;
 import com.android.systemui.clipboardoverlay.dagger.ClipboardOverlayModule.OverlayWindowContext;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.screenshot.TimeoutHandler;
 
 import java.io.IOException;
@@ -101,6 +103,8 @@
     private final ClipboardOverlayWindow mWindow;
     private final TimeoutHandler mTimeoutHandler;
     private final TextClassifier mTextClassifier;
+    private final ClipboardOverlayUtils mClipboardUtils;
+    private final FeatureFlags mFeatureFlags;
 
     private final ClipboardOverlayView mView;
 
@@ -119,11 +123,15 @@
     private Animator mExitAnimator;
     private Animator mEnterAnimator;
 
+    private Runnable mOnUiUpdate;
+
     private final ClipboardOverlayView.ClipboardOverlayCallbacks mClipboardCallbacks =
             new ClipboardOverlayView.ClipboardOverlayCallbacks() {
                 @Override
                 public void onInteraction() {
-                    mTimeoutHandler.resetTimeout();
+                    if (mOnUiUpdate != null) {
+                        mOnUiUpdate.run();
+                    }
                 }
 
                 @Override
@@ -178,7 +186,10 @@
             ClipboardOverlayWindow clipboardOverlayWindow,
             BroadcastDispatcher broadcastDispatcher,
             BroadcastSender broadcastSender,
-            TimeoutHandler timeoutHandler, UiEventLogger uiEventLogger) {
+            TimeoutHandler timeoutHandler,
+            FeatureFlags featureFlags,
+            ClipboardOverlayUtils clipboardUtils,
+            UiEventLogger uiEventLogger) {
         mBroadcastDispatcher = broadcastDispatcher;
         mDisplayManager = requireNonNull(context.getSystemService(DisplayManager.class));
         final Context displayContext = context.createDisplayContext(getDefaultDisplay());
@@ -199,6 +210,9 @@
         mTimeoutHandler = timeoutHandler;
         mTimeoutHandler.setDefaultTimeoutMillis(CLIPBOARD_DEFAULT_TIMEOUT_MILLIS);
 
+        mFeatureFlags = featureFlags;
+        mClipboardUtils = clipboardUtils;
+
         mView.setCallbacks(mClipboardCallbacks);
 
 
@@ -257,11 +271,13 @@
         boolean isSensitive = clipData != null && clipData.getDescription().getExtras() != null
                 && clipData.getDescription().getExtras()
                 .getBoolean(ClipDescription.EXTRA_IS_SENSITIVE);
+        boolean isRemote = mFeatureFlags.isEnabled(CLIPBOARD_REMOTE_BEHAVIOR)
+                && mClipboardUtils.isRemoteCopy(mContext, clipData, clipSource);
         if (clipData == null || clipData.getItemCount() == 0) {
             mView.showDefaultTextPreview();
         } else if (!TextUtils.isEmpty(clipData.getItemAt(0).getText())) {
             ClipData.Item item = clipData.getItemAt(0);
-            if (DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+            if (isRemote || DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
                     CLIPBOARD_OVERLAY_SHOW_ACTIONS, false)) {
                 if (item.getTextLinks() != null) {
                     AsyncTask.execute(() -> classifyText(clipData.getItemAt(0), clipSource));
@@ -287,7 +303,13 @@
         maybeShowRemoteCopy(clipData);
         animateIn();
         mView.announceForAccessibility(accessibilityAnnouncement);
-        mTimeoutHandler.resetTimeout();
+        if (isRemote) {
+            mTimeoutHandler.cancelTimeout();
+            mOnUiUpdate = null;
+        } else {
+            mOnUiUpdate = mTimeoutHandler::resetTimeout;
+            mOnUiUpdate.run();
+        }
     }
 
     private void maybeShowRemoteCopy(ClipData clipData) {
@@ -427,7 +449,9 @@
             @Override
             public void onAnimationEnd(Animator animation) {
                 super.onAnimationEnd(animation);
-                mTimeoutHandler.resetTimeout();
+                if (mOnUiUpdate != null) {
+                    mOnUiUpdate.run();
+                }
             }
         });
         mEnterAnimator.start();
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtils.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtils.java
new file mode 100644
index 0000000..cece764
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtils.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 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.clipboardoverlay;
+
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.content.ComponentName;
+import android.content.Context;
+
+import com.android.systemui.R;
+
+import javax.inject.Inject;
+
+class ClipboardOverlayUtils {
+
+    @Inject
+    ClipboardOverlayUtils() {
+    }
+
+    boolean isRemoteCopy(Context context, ClipData clipData, String clipSource) {
+        if (clipData != null && clipData.getDescription().getExtras() != null
+                && clipData.getDescription().getExtras().getBoolean(
+                ClipDescription.EXTRA_IS_REMOTE_DEVICE)) {
+            ComponentName remoteComponent = ComponentName.unflattenFromString(
+                    context.getResources().getString(R.string.config_remoteCopyPackage));
+            if (remoteComponent != null) {
+                return remoteComponent.getPackageName().equals(clipSource);
+            }
+        }
+        return false;
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
index bf7d716..6cb0e8b 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsUiControllerImpl.kt
@@ -24,7 +24,6 @@
 import android.content.ComponentName
 import android.content.Context
 import android.content.Intent
-import android.content.SharedPreferences
 import android.graphics.drawable.Drawable
 import android.graphics.drawable.LayerDrawable
 import android.service.controls.Control
@@ -59,7 +58,10 @@
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.globalactions.GlobalActionsPopupMenu
 import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.shade.ShadeController
+import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.concurrency.DelayableExecutor
 import dagger.Lazy
@@ -76,13 +78,14 @@
         @Main val uiExecutor: DelayableExecutor,
         @Background val bgExecutor: DelayableExecutor,
         val controlsListingController: Lazy<ControlsListingController>,
-        @Main val sharedPreferences: SharedPreferences,
         val controlActionCoordinator: ControlActionCoordinator,
         private val activityStarter: ActivityStarter,
         private val shadeController: ShadeController,
         private val iconCache: CustomIconCache,
         private val controlsMetricsLogger: ControlsMetricsLogger,
-        private val keyguardStateController: KeyguardStateController
+        private val keyguardStateController: KeyguardStateController,
+        private val userFileManager: UserFileManager,
+        private val userTracker: UserTracker,
 ) : ControlsUiController {
 
     companion object {
@@ -110,6 +113,12 @@
     private lateinit var onDismiss: Runnable
     private val popupThemedContext = ContextThemeWrapper(context, R.style.Control_ListPopupWindow)
     private var retainCache = false
+    private val sharedPreferences
+        get() = userFileManager.getSharedPreferences(
+            fileName = DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
+            mode = 0,
+            userId = userTracker.userId
+        )
 
     private val collator = Collator.getInstance(context.resources.configuration.locales[0])
     private val localeComparator = compareBy<SelectionItem, CharSequence>(collator) {
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
index fb01691..4eb444e 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/DefaultActivityBinder.java
@@ -25,6 +25,7 @@
 import com.android.systemui.people.widget.LaunchConversationActivity;
 import com.android.systemui.screenshot.LongScreenshotActivity;
 import com.android.systemui.sensorprivacy.SensorUseStartedActivity;
+import com.android.systemui.sensorprivacy.television.TvSensorPrivacyChangedActivity;
 import com.android.systemui.sensorprivacy.television.TvUnblockSensorActivity;
 import com.android.systemui.settings.brightness.BrightnessDialog;
 import com.android.systemui.statusbar.tv.notifications.TvNotificationPanelActivity;
@@ -142,4 +143,11 @@
     @ClassKey(HdmiCecSetMenuLanguageActivity.class)
     public abstract Activity bindHdmiCecSetMenuLanguageActivity(
             HdmiCecSetMenuLanguageActivity activity);
+
+    /** Inject into TvSensorPrivacyChangedActivity. */
+    @Binds
+    @IntoMap
+    @ClassKey(TvSensorPrivacyChangedActivity.class)
+    public abstract Activity bindTvSensorPrivacyChangedActivity(
+            TvSensorPrivacyChangedActivity activity);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index 139a8b7..25418c3 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -20,6 +20,7 @@
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
 import android.app.AlarmManager;
+import android.app.AppOpsManager;
 import android.app.IActivityManager;
 import android.app.IActivityTaskManager;
 import android.app.INotificationManager;
@@ -137,6 +138,12 @@
 
     @Provides
     @Singleton
+    static AppOpsManager provideAppOpsManager(Context context) {
+        return context.getSystemService(AppOpsManager.class);
+    }
+
+    @Provides
+    @Singleton
     static AudioManager provideAudioManager(Context context) {
         return context.getSystemService(AudioManager.class);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
index 48bef97..2bee75e 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/ReferenceSystemUIModule.java
@@ -41,6 +41,7 @@
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsImplementation;
 import com.android.systemui.screenshot.ReferenceScreenshotModule;
+import com.android.systemui.settings.dagger.MultiUserUtilsModule;
 import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.shade.ShadeControllerImpl;
@@ -93,6 +94,7 @@
         AospPolicyModule.class,
         GestureModule.class,
         MediaModule.class,
+        MultiUserUtilsModule.class,
         PowerModule.class,
         QSModule.class,
         ReferenceScreenshotModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index e47e636..271a8c5ff 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -47,6 +47,7 @@
 import com.android.systemui.log.dagger.LogModule;
 import com.android.systemui.mediaprojection.appselector.MediaProjectionModule;
 import com.android.systemui.model.SysUiState;
+import com.android.systemui.motiontool.MotionToolModule;
 import com.android.systemui.navigationbar.NavigationBarComponent;
 import com.android.systemui.notetask.NoteTaskModule;
 import com.android.systemui.people.PeopleModule;
@@ -58,7 +59,6 @@
 import com.android.systemui.recents.Recents;
 import com.android.systemui.screenshot.dagger.ScreenshotModule;
 import com.android.systemui.security.data.repository.SecurityRepositoryModule;
-import com.android.systemui.settings.dagger.MultiUserUtilsModule;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.smartspace.dagger.SmartspaceModule;
 import com.android.systemui.statusbar.CommandQueue;
@@ -84,6 +84,7 @@
 import com.android.systemui.statusbar.policy.dagger.StatusBarPolicyModule;
 import com.android.systemui.statusbar.window.StatusBarWindowModule;
 import com.android.systemui.telephony.data.repository.TelephonyRepositoryModule;
+import com.android.systemui.temporarydisplay.dagger.TemporaryDisplayModule;
 import com.android.systemui.tuner.dagger.TunerModule;
 import com.android.systemui.unfold.SysUIUnfoldModule;
 import com.android.systemui.user.UserModule;
@@ -133,13 +134,13 @@
             FooterActionsModule.class,
             LogModule.class,
             MediaProjectionModule.class,
+            MotionToolModule.class,
             PeopleHubModule.class,
             PeopleModule.class,
             PluginModule.class,
             PrivacyModule.class,
             ScreenshotModule.class,
             SensorModule.class,
-            MultiUserUtilsModule.class,
             SecurityRepositoryModule.class,
             SettingsUtilModule.class,
             SmartRepliesInflationModule.class,
@@ -150,6 +151,7 @@
             SysUIConcurrencyModule.class,
             SysUIUnfoldModule.class,
             TelephonyRepositoryModule.class,
+            TemporaryDisplayModule.class,
             TunerModule.class,
             UserModule.class,
             UtilModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
index 60227ee..937884c 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeScreenBrightness.java
@@ -171,7 +171,10 @@
 
     @Override
     public void onSensorChanged(SensorEvent event) {
-        Trace.beginSection("DozeScreenBrightness.onSensorChanged" + event.values[0]);
+        if (Trace.isEnabled()) {
+            Trace.traceBegin(
+                    Trace.TRACE_TAG_APP, "DozeScreenBrightness.onSensorChanged" + event.values[0]);
+        }
         try {
             if (mRegistered) {
                 mLastSensorValue = (int) event.values[0];
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
new file mode 100644
index 0000000..d8dd6a2
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayAnimationsController.kt
@@ -0,0 +1,147 @@
+/*
+ * Copyright (C) 2022 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.dreams
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.AnimatorSet
+import android.animation.ValueAnimator
+import android.view.View
+import androidx.core.animation.doOnEnd
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.dreams.complication.ComplicationHostViewController
+import com.android.systemui.dreams.complication.ComplicationLayoutParams
+import com.android.systemui.dreams.dagger.DreamOverlayModule
+import com.android.systemui.statusbar.BlurUtils
+import java.util.function.Consumer
+import javax.inject.Inject
+import javax.inject.Named
+
+/** Controller for dream overlay animations. */
+class DreamOverlayAnimationsController
+@Inject
+constructor(
+    private val mBlurUtils: BlurUtils,
+    private val mComplicationHostViewController: ComplicationHostViewController,
+    private val mStatusBarViewController: DreamOverlayStatusBarViewController,
+    private val mOverlayStateController: DreamOverlayStateController,
+    @Named(DreamOverlayModule.DREAM_IN_BLUR_ANIMATION_DURATION)
+    private val mDreamInBlurAnimDuration: Int,
+    @Named(DreamOverlayModule.DREAM_IN_BLUR_ANIMATION_DELAY) private val mDreamInBlurAnimDelay: Int,
+    @Named(DreamOverlayModule.DREAM_IN_COMPLICATIONS_ANIMATION_DURATION)
+    private val mDreamInComplicationsAnimDuration: Int,
+    @Named(DreamOverlayModule.DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY)
+    private val mDreamInTopComplicationsAnimDelay: Int,
+    @Named(DreamOverlayModule.DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY)
+    private val mDreamInBottomComplicationsAnimDelay: Int
+) {
+
+    var mEntryAnimations: AnimatorSet? = null
+
+    /** Starts the dream content and dream overlay entry animations. */
+    fun startEntryAnimations(view: View) {
+        cancelRunningEntryAnimations()
+
+        mEntryAnimations = AnimatorSet()
+        mEntryAnimations?.apply {
+            playTogether(
+                buildDreamInBlurAnimator(view),
+                buildDreamInTopComplicationsAnimator(),
+                buildDreamInBottomComplicationsAnimator()
+            )
+            doOnEnd { mOverlayStateController.setEntryAnimationsFinished(true) }
+            start()
+        }
+    }
+
+    /** Cancels the dream content and dream overlay animations, if they're currently running. */
+    fun cancelRunningEntryAnimations() {
+        if (mEntryAnimations?.isRunning == true) {
+            mEntryAnimations?.cancel()
+        }
+        mEntryAnimations = null
+    }
+
+    private fun buildDreamInBlurAnimator(view: View): Animator {
+        return ValueAnimator.ofFloat(1f, 0f).apply {
+            duration = mDreamInBlurAnimDuration.toLong()
+            startDelay = mDreamInBlurAnimDelay.toLong()
+            interpolator = Interpolators.LINEAR
+            addUpdateListener { animator: ValueAnimator ->
+                mBlurUtils.applyBlur(
+                    view.viewRootImpl,
+                    mBlurUtils.blurRadiusOfRatio(animator.animatedValue as Float).toInt(),
+                    false /*opaque*/
+                )
+            }
+        }
+    }
+
+    private fun buildDreamInTopComplicationsAnimator(): Animator {
+        return ValueAnimator.ofFloat(0f, 1f).apply {
+            duration = mDreamInComplicationsAnimDuration.toLong()
+            startDelay = mDreamInTopComplicationsAnimDelay.toLong()
+            interpolator = Interpolators.LINEAR
+            addUpdateListener { va: ValueAnimator ->
+                setTopElementsAlpha(va.animatedValue as Float)
+            }
+        }
+    }
+
+    private fun buildDreamInBottomComplicationsAnimator(): Animator {
+        return ValueAnimator.ofFloat(0f, 1f).apply {
+            duration = mDreamInComplicationsAnimDuration.toLong()
+            startDelay = mDreamInBottomComplicationsAnimDelay.toLong()
+            interpolator = Interpolators.LINEAR
+            addUpdateListener { va: ValueAnimator ->
+                setBottomElementsAlpha(va.animatedValue as Float)
+            }
+            addListener(
+                object : AnimatorListenerAdapter() {
+                    override fun onAnimationStart(animation: Animator) {
+                        mComplicationHostViewController
+                            .getViewsAtPosition(ComplicationLayoutParams.POSITION_BOTTOM)
+                            .forEach(Consumer { v: View -> v.visibility = View.VISIBLE })
+                    }
+                }
+            )
+        }
+    }
+
+    /** Sets alpha of top complications and the status bar. */
+    private fun setTopElementsAlpha(alpha: Float) {
+        mComplicationHostViewController
+            .getViewsAtPosition(ComplicationLayoutParams.POSITION_TOP)
+            .forEach(Consumer { v: View -> setAlphaAndEnsureVisible(v, alpha) })
+        mStatusBarViewController.setAlpha(alpha)
+    }
+
+    /** Sets alpha of bottom complications. */
+    private fun setBottomElementsAlpha(alpha: Float) {
+        mComplicationHostViewController
+            .getViewsAtPosition(ComplicationLayoutParams.POSITION_BOTTOM)
+            .forEach(Consumer { v: View -> setAlphaAndEnsureVisible(v, alpha) })
+    }
+
+    private fun setAlphaAndEnsureVisible(view: View, alpha: Float) {
+        if (alpha > 0 && view.visibility != View.VISIBLE) {
+            view.visibility = View.VISIBLE
+        }
+
+        view.alpha = alpha
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
index 733a80d..d0d0184 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayContainerViewController.java
@@ -54,6 +54,8 @@
     private final DreamOverlayStatusBarViewController mStatusBarViewController;
     private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     private final BlurUtils mBlurUtils;
+    private final DreamOverlayAnimationsController mDreamOverlayAnimationsController;
+    private final DreamOverlayStateController mStateController;
 
     private final ComplicationHostViewController mComplicationHostViewController;
 
@@ -134,12 +136,16 @@
             @Named(DreamOverlayModule.BURN_IN_PROTECTION_UPDATE_INTERVAL) long
                     burnInProtectionUpdateInterval,
             @Named(DreamOverlayModule.MILLIS_UNTIL_FULL_JITTER) long millisUntilFullJitter,
-            BouncerCallbackInteractor bouncerCallbackInteractor) {
+            BouncerCallbackInteractor bouncerCallbackInteractor,
+            DreamOverlayAnimationsController animationsController,
+            DreamOverlayStateController stateController) {
         super(containerView);
         mDreamOverlayContentView = contentView;
         mStatusBarViewController = statusBarViewController;
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
         mBlurUtils = blurUtils;
+        mDreamOverlayAnimationsController = animationsController;
+        mStateController = stateController;
 
         mComplicationHostViewController = complicationHostViewController;
         mDreamOverlayMaxTranslationY = resources.getDimensionPixelSize(
@@ -172,6 +178,11 @@
             bouncer.addBouncerExpansionCallback(mBouncerExpansionCallback);
         }
         mBouncerCallbackInteractor.addBouncerExpansionCallback(mBouncerExpansionCallback);
+
+        // Start dream entry animations. Skip animations for low light clock.
+        if (!mStateController.isLowLightActive()) {
+            mDreamOverlayAnimationsController.startEntryAnimations(mView);
+        }
     }
 
     @Override
@@ -182,6 +193,8 @@
             bouncer.removeBouncerExpansionCallback(mBouncerExpansionCallback);
         }
         mBouncerCallbackInteractor.removeBouncerExpansionCallback(mBouncerExpansionCallback);
+
+        mDreamOverlayAnimationsController.cancelRunningEntryAnimations();
     }
 
     View getContainerView() {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index d1b7368..8542412 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -90,13 +90,15 @@
             new KeyguardUpdateMonitorCallback() {
                 @Override
                 public void onShadeExpandedChanged(boolean expanded) {
-                    if (mLifecycleRegistry.getCurrentState() != Lifecycle.State.RESUMED
-                            && mLifecycleRegistry.getCurrentState() != Lifecycle.State.STARTED) {
-                        return;
-                    }
+                    mExecutor.execute(() -> {
+                        if (getCurrentStateLocked() != Lifecycle.State.RESUMED
+                                && getCurrentStateLocked() != Lifecycle.State.STARTED) {
+                            return;
+                        }
 
-                    mLifecycleRegistry.setCurrentState(
-                            expanded ? Lifecycle.State.STARTED : Lifecycle.State.RESUMED);
+                        setCurrentStateLocked(
+                                expanded ? Lifecycle.State.STARTED : Lifecycle.State.RESUMED);
+                    });
                 }
             };
 
@@ -146,29 +148,30 @@
                 () -> mExecutor.execute(DreamOverlayService.this::requestExit);
         mDreamOverlayComponent = dreamOverlayComponentFactory.create(viewModelStore, host);
         mLifecycleRegistry = mDreamOverlayComponent.getLifecycleRegistry();
-        setCurrentState(Lifecycle.State.CREATED);
-    }
 
-    private void setCurrentState(Lifecycle.State state) {
-        mExecutor.execute(() -> mLifecycleRegistry.setCurrentState(state));
+        mExecutor.execute(() -> setCurrentStateLocked(Lifecycle.State.CREATED));
     }
 
     @Override
     public void onDestroy() {
         mKeyguardUpdateMonitor.removeCallback(mKeyguardCallback);
-        setCurrentState(Lifecycle.State.DESTROYED);
 
-        resetCurrentDreamOverlay();
+        mExecutor.execute(() -> {
+            setCurrentStateLocked(Lifecycle.State.DESTROYED);
 
-        mDestroyed = true;
+            resetCurrentDreamOverlayLocked();
+
+            mDestroyed = true;
+        });
+
         super.onDestroy();
     }
 
     @Override
     public void onStartDream(@NonNull WindowManager.LayoutParams layoutParams) {
-        setCurrentState(Lifecycle.State.STARTED);
-
         mExecutor.execute(() -> {
+            setCurrentStateLocked(Lifecycle.State.STARTED);
+
             mUiEventLogger.log(DreamOverlayEvent.DREAM_OVERLAY_ENTER_START);
 
             if (mDestroyed) {
@@ -181,7 +184,7 @@
                 // Reset the current dream overlay before starting a new one. This can happen
                 // when two dreams overlap (briefly, for a smoother dream transition) and both
                 // dreams are bound to the dream overlay service.
-                resetCurrentDreamOverlay();
+                resetCurrentDreamOverlayLocked();
             }
 
             mDreamOverlayContainerViewController =
@@ -191,7 +194,7 @@
 
             mStateController.setShouldShowComplications(shouldShowComplications());
             addOverlayWindowLocked(layoutParams);
-            setCurrentState(Lifecycle.State.RESUMED);
+            setCurrentStateLocked(Lifecycle.State.RESUMED);
             mStateController.setOverlayActive(true);
             final ComponentName dreamComponent = getDreamComponent();
             mStateController.setLowLightActive(
@@ -202,6 +205,14 @@
         });
     }
 
+    private Lifecycle.State getCurrentStateLocked() {
+        return mLifecycleRegistry.getCurrentState();
+    }
+
+    private void setCurrentStateLocked(Lifecycle.State state) {
+        mLifecycleRegistry.setCurrentState(state);
+    }
+
     /**
      * Inserts {@link Window} to host the dream overlay into the dream's parent window. Must be
      * called from the main executing thread. The window attributes closely mirror those that are
@@ -231,13 +242,13 @@
         // Make extra sure the container view has been removed from its old parent (otherwise we
         // risk an IllegalStateException in some cases when setting the container view as the
         // window's content view and the container view hasn't been properly removed previously).
-        removeContainerViewFromParent();
+        removeContainerViewFromParentLocked();
         mWindow.setContentView(mDreamOverlayContainerViewController.getContainerView());
 
         mWindowManager.addView(mWindow.getDecorView(), mWindow.getAttributes());
     }
 
-    private void removeContainerViewFromParent() {
+    private void removeContainerViewFromParentLocked() {
         View containerView = mDreamOverlayContainerViewController.getContainerView();
         if (containerView == null) {
             return;
@@ -250,13 +261,14 @@
         parentView.removeView(containerView);
     }
 
-    private void resetCurrentDreamOverlay() {
+    private void resetCurrentDreamOverlayLocked() {
         if (mStarted && mWindow != null) {
             mWindowManager.removeView(mWindow.getDecorView());
         }
 
         mStateController.setOverlayActive(false);
         mStateController.setLowLightActive(false);
+        mStateController.setEntryAnimationsFinished(false);
 
         mDreamOverlayContainerViewController = null;
         mDreamOverlayTouchMonitor = null;
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
index 72feaca..e80d0be 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStateController.java
@@ -51,6 +51,7 @@
 
     public static final int STATE_DREAM_OVERLAY_ACTIVE = 1 << 0;
     public static final int STATE_LOW_LIGHT_ACTIVE = 1 << 1;
+    public static final int STATE_DREAM_ENTRY_ANIMATIONS_FINISHED = 1 << 2;
 
     private static final int OP_CLEAR_STATE = 1;
     private static final int OP_SET_STATE = 2;
@@ -202,6 +203,14 @@
         return containsState(STATE_LOW_LIGHT_ACTIVE);
     }
 
+    /**
+     * Returns whether the dream content and dream overlay entry animations are finished.
+     * @return {@code true} if animations are finished, {@code false} otherwise.
+     */
+    public boolean areEntryAnimationsFinished() {
+        return containsState(STATE_DREAM_ENTRY_ANIMATIONS_FINISHED);
+    }
+
     private boolean containsState(int state) {
         return (mState & state) != 0;
     }
@@ -218,7 +227,7 @@
         }
 
         if (existingState != mState) {
-            notifyCallbacks(callback -> callback.onStateChanged());
+            notifyCallbacks(Callback::onStateChanged);
         }
     }
 
@@ -239,6 +248,15 @@
     }
 
     /**
+     * Sets whether dream content and dream overlay entry animations are finished.
+     * @param finished {@code true} if entry animations are finished, {@code false} otherwise.
+     */
+    public void setEntryAnimationsFinished(boolean finished) {
+        modifyState(finished ? OP_SET_STATE : OP_CLEAR_STATE,
+                STATE_DREAM_ENTRY_ANIMATIONS_FINISHED);
+    }
+
+    /**
      * Returns the available complication types.
      */
     @Complication.ComplicationType
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
index bb1c430..d17fbe3 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayStatusBarViewController.java
@@ -16,10 +16,6 @@
 
 package com.android.systemui.dreams;
 
-import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
-import static android.app.StatusBarManager.WINDOW_STATE_HIDING;
-import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
-
 import android.app.AlarmManager;
 import android.app.StatusBarManager;
 import android.content.res.Resources;
@@ -83,6 +79,9 @@
 
     private boolean mIsAttached;
 
+    // Whether dream entry animations are finished.
+    private boolean mEntryAnimationsFinished = false;
+
     private final NetworkRequest mNetworkRequest = new NetworkRequest.Builder()
             .clearCapabilities()
             .addTransportType(NetworkCapabilities.TRANSPORT_WIFI).build();
@@ -109,7 +108,9 @@
             new DreamOverlayStateController.Callback() {
                 @Override
                 public void onStateChanged() {
-                    updateLowLightState();
+                    mEntryAnimationsFinished =
+                            mDreamOverlayStateController.areEntryAnimationsFinished();
+                    updateVisibility();
                 }
             };
 
@@ -195,7 +196,6 @@
         mStatusBarItemsProvider.addCallback(mStatusBarItemsProviderCallback);
 
         mDreamOverlayStateController.addCallback(mDreamOverlayStateCallback);
-        updateLowLightState();
 
         mTouchInsetSession.addViewToTracking(mView);
     }
@@ -216,6 +216,26 @@
         mIsAttached = false;
     }
 
+    /**
+     * Sets alpha of the dream overlay status bar.
+     *
+     * No-op if the dream overlay status bar should not be shown.
+     */
+    protected void setAlpha(float alpha) {
+        updateVisibility();
+
+        if (mView.getVisibility() != View.VISIBLE) {
+            return;
+        }
+
+        mView.setAlpha(alpha);
+    }
+
+    private boolean shouldShowStatusBar() {
+        return !mDreamOverlayStateController.isLowLightActive()
+                && !mStatusBarWindowStateController.windowIsShowing();
+    }
+
     private void updateWifiUnavailableStatusIcon() {
         final NetworkCapabilities capabilities =
                 mConnectivityManager.getNetworkCapabilities(
@@ -235,13 +255,12 @@
                 hasAlarm ? buildAlarmContentDescription(alarm) : null);
     }
 
-    private void updateLowLightState() {
-        int visibility = View.VISIBLE;
-        if (mDreamOverlayStateController.isLowLightActive()
-                || mStatusBarWindowStateController.windowIsShowing()) {
-            visibility = View.INVISIBLE;
+    private void updateVisibility() {
+        if (shouldShowStatusBar()) {
+            mView.setVisibility(View.VISIBLE);
+        } else {
+            mView.setVisibility(View.INVISIBLE);
         }
-        mView.setVisibility(visibility);
     }
 
     private String buildAlarmContentDescription(AlarmManager.AlarmClockInfo alarm) {
@@ -298,21 +317,11 @@
     }
 
     private void onSystemStatusBarStateChanged(@StatusBarManager.WindowVisibleState int state) {
-        mMainExecutor.execute(() -> {
-            if (!mIsAttached || mDreamOverlayStateController.isLowLightActive()) {
-                return;
-            }
+        if (!mIsAttached || !mEntryAnimationsFinished) {
+            return;
+        }
 
-            switch (state) {
-                case WINDOW_STATE_SHOWING:
-                    mView.setVisibility(View.INVISIBLE);
-                    break;
-                case WINDOW_STATE_HIDING:
-                case WINDOW_STATE_HIDDEN:
-                    mView.setVisibility(View.VISIBLE);
-                    break;
-            }
-        });
+        mMainExecutor.execute(this::updateVisibility);
     }
 
     private void onStatusBarItemsChanged(List<StatusBarItem> newItems) {
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java
index fd6cfc0..100ccc3 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/complication/ComplicationHostViewController.java
@@ -28,6 +28,7 @@
 import androidx.constraintlayout.widget.ConstraintLayout;
 import androidx.lifecycle.LifecycleOwner;
 
+import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.util.ViewController;
 
 import java.util.Collection;
@@ -49,20 +50,34 @@
     private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
 
     private final ComplicationLayoutEngine mLayoutEngine;
+    private final DreamOverlayStateController mDreamOverlayStateController;
     private final LifecycleOwner mLifecycleOwner;
     private final ComplicationCollectionViewModel mComplicationCollectionViewModel;
     private final HashMap<ComplicationId, Complication.ViewHolder> mComplications = new HashMap<>();
 
+    // Whether dream entry animations are finished.
+    private boolean mEntryAnimationsFinished = false;
+
     @Inject
     protected ComplicationHostViewController(
             @Named(SCOPED_COMPLICATIONS_LAYOUT) ConstraintLayout view,
             ComplicationLayoutEngine layoutEngine,
+            DreamOverlayStateController dreamOverlayStateController,
             LifecycleOwner lifecycleOwner,
             @Named(SCOPED_COMPLICATIONS_MODEL) ComplicationCollectionViewModel viewModel) {
         super(view);
         mLayoutEngine = layoutEngine;
         mLifecycleOwner = lifecycleOwner;
         mComplicationCollectionViewModel = viewModel;
+        mDreamOverlayStateController = dreamOverlayStateController;
+
+        mDreamOverlayStateController.addCallback(new DreamOverlayStateController.Callback() {
+            @Override
+            public void onStateChanged() {
+                mEntryAnimationsFinished =
+                        mDreamOverlayStateController.areEntryAnimationsFinished();
+            }
+        });
     }
 
     @Override
@@ -123,6 +138,11 @@
                     final ComplicationId id = complication.getId();
                     final Complication.ViewHolder viewHolder = complication.getComplication()
                             .createView(complication);
+                    // Complications to be added before dream entry animations are finished are set
+                    // to invisible and are animated in.
+                    if (!mEntryAnimationsFinished) {
+                        viewHolder.getView().setVisibility(View.INVISIBLE);
+                    }
                     mComplications.put(id, viewHolder);
                     if (viewHolder.getView().getParent() != null) {
                         Log.e(TAG, "View for complication "
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
index 4fe1622..cb012fa 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/dagger/DreamOverlayModule.java
@@ -47,6 +47,14 @@
     public static final String BURN_IN_PROTECTION_UPDATE_INTERVAL =
             "burn_in_protection_update_interval";
     public static final String MILLIS_UNTIL_FULL_JITTER = "millis_until_full_jitter";
+    public static final String DREAM_IN_BLUR_ANIMATION_DURATION = "dream_in_blur_anim_duration";
+    public static final String DREAM_IN_BLUR_ANIMATION_DELAY = "dream_in_blur_anim_delay";
+    public static final String DREAM_IN_COMPLICATIONS_ANIMATION_DURATION =
+            "dream_in_complications_anim_duration";
+    public static final String DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY =
+            "dream_in_top_complications_anim_delay";
+    public static final String DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY =
+            "dream_in_bottom_complications_anim_delay";
 
     /** */
     @Provides
@@ -114,6 +122,51 @@
         return resources.getInteger(R.integer.config_dreamOverlayMillisUntilFullJitter);
     }
 
+    /**
+     * Duration in milliseconds of the dream in un-blur animation.
+     */
+    @Provides
+    @Named(DREAM_IN_BLUR_ANIMATION_DURATION)
+    static int providesDreamInBlurAnimationDuration(@Main Resources resources) {
+        return resources.getInteger(R.integer.config_dreamOverlayInBlurDurationMs);
+    }
+
+    /**
+     * Delay in milliseconds of the dream in un-blur animation.
+     */
+    @Provides
+    @Named(DREAM_IN_BLUR_ANIMATION_DELAY)
+    static int providesDreamInBlurAnimationDelay(@Main Resources resources) {
+        return resources.getInteger(R.integer.config_dreamOverlayInBlurDelayMs);
+    }
+
+    /**
+     * Duration in milliseconds of the dream in complications fade-in animation.
+     */
+    @Provides
+    @Named(DREAM_IN_COMPLICATIONS_ANIMATION_DURATION)
+    static int providesDreamInComplicationsAnimationDuration(@Main Resources resources) {
+        return resources.getInteger(R.integer.config_dreamOverlayInComplicationsDurationMs);
+    }
+
+    /**
+     * Delay in milliseconds of the dream in top complications fade-in animation.
+     */
+    @Provides
+    @Named(DREAM_IN_TOP_COMPLICATIONS_ANIMATION_DELAY)
+    static int providesDreamInTopComplicationsAnimationDelay(@Main Resources resources) {
+        return resources.getInteger(R.integer.config_dreamOverlayInTopComplicationsDelayMs);
+    }
+
+    /**
+     * Delay in milliseconds of the dream in bottom complications fade-in animation.
+     */
+    @Provides
+    @Named(DREAM_IN_BOTTOM_COMPLICATIONS_ANIMATION_DELAY)
+    static int providesDreamInBottomComplicationsAnimationDelay(@Main Resources resources) {
+        return resources.getInteger(R.integer.config_dreamOverlayInBottomComplicationsDelayMs);
+    }
+
     @Provides
     @DreamOverlayComponent.DreamOverlayScope
     static LifecycleOwner providesLifecycleOwner(Lazy<LifecycleRegistry> lifecycleRegistryLazy) {
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
index 3adeeac..1f061e9 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebug.java
@@ -21,6 +21,7 @@
 import static com.android.systemui.flags.FlagManager.EXTRA_FLAGS;
 import static com.android.systemui.flags.FlagManager.EXTRA_ID;
 import static com.android.systemui.flags.FlagManager.EXTRA_VALUE;
+import static com.android.systemui.flags.FlagsCommonModule.ALL_FLAGS;
 
 import static java.util.Objects.requireNonNull;
 
@@ -59,20 +60,20 @@
  *
  * Flags can be set (or unset) via the following adb command:
  *
- *   adb shell cmd statusbar flag <id> <on|off|toggle|erase>
+ * adb shell cmd statusbar flag <id> <on|off|toggle|erase>
  *
- *  Alternatively, you can change flags via a broadcast intent:
+ * Alternatively, you can change flags via a broadcast intent:
  *
- *   adb shell am broadcast -a com.android.systemui.action.SET_FLAG --ei id <id> [--ez value <0|1>]
+ * adb shell am broadcast -a com.android.systemui.action.SET_FLAG --ei id <id> [--ez value <0|1>]
  *
  * To restore a flag back to its default, leave the `--ez value <0|1>` off of the command.
  */
 @SysUISingleton
 public class FeatureFlagsDebug implements FeatureFlags {
     static final String TAG = "SysUIFlags";
-    static final String ALL_FLAGS = "all_flags";
 
     private final FlagManager mFlagManager;
+    private final Context mContext;
     private final SecureSettings mSecureSettings;
     private final Resources mResources;
     private final SystemPropertiesHelper mSystemProperties;
@@ -83,6 +84,14 @@
     private final Map<Integer, String> mStringFlagCache = new TreeMap<>();
     private final Restarter mRestarter;
 
+    private final ServerFlagReader.ChangeListener mOnPropertiesChanged =
+            new ServerFlagReader.ChangeListener() {
+                @Override
+                public void onChange() {
+                    mRestarter.restart();
+                }
+            };
+
     @Inject
     public FeatureFlagsDebug(
             FlagManager flagManager,
@@ -93,23 +102,28 @@
             DeviceConfigProxy deviceConfigProxy,
             ServerFlagReader serverFlagReader,
             @Named(ALL_FLAGS) Map<Integer, Flag<?>> allFlags,
-            Restarter barService) {
+            Restarter restarter) {
         mFlagManager = flagManager;
+        mContext = context;
         mSecureSettings = secureSettings;
         mResources = resources;
         mSystemProperties = systemProperties;
         mDeviceConfigProxy = deviceConfigProxy;
         mServerFlagReader = serverFlagReader;
         mAllFlags = allFlags;
-        mRestarter = barService;
+        mRestarter = restarter;
+    }
 
+    /** Call after construction to setup listeners. */
+    void init() {
         IntentFilter filter = new IntentFilter();
         filter.addAction(ACTION_SET_FLAG);
         filter.addAction(ACTION_GET_FLAGS);
-        flagManager.setOnSettingsChangedAction(this::restartSystemUI);
-        flagManager.setClearCacheAction(this::removeFromCache);
-        context.registerReceiver(mReceiver, filter, null, null,
+        mFlagManager.setOnSettingsChangedAction(this::restartSystemUI);
+        mFlagManager.setClearCacheAction(this::removeFromCache);
+        mContext.registerReceiver(mReceiver, filter, null, null,
                 Context.RECEIVER_EXPORTED_UNAUDITED);
+        mServerFlagReader.listenForChanges(mAllFlags.values(), mOnPropertiesChanged);
     }
 
     @Override
@@ -196,7 +210,7 @@
         return mStringFlagCache.get(id);
     }
 
-    /** Specific override for Boolean flags that checks against the teamfood list.*/
+    /** Specific override for Boolean flags that checks against the teamfood list. */
     private boolean readFlagValue(int id, boolean defaultValue) {
         Boolean result = readBooleanFlagOverride(id);
         boolean hasServerOverride = mServerFlagReader.hasOverride(id);
@@ -273,6 +287,7 @@
     private void dispatchListenersAndMaybeRestart(int id, Consumer<Boolean> restartAction) {
         mFlagManager.dispatchListenersAndMaybeRestart(id, restartAction);
     }
+
     /** Works just like {@link #eraseFlag(int)} except that it doesn't restart SystemUI. */
     private void eraseInternal(int id) {
         // We can't actually "erase" things from sysprops, but we can set them to empty!
@@ -358,7 +373,7 @@
                     }
                 }
 
-                Bundle extras =  getResultExtras(true);
+                Bundle extras = getResultExtras(true);
                 if (extras != null) {
                     extras.putParcelableArrayList(EXTRA_FLAGS, pFlags);
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt
index 560dcbd..6271334 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsDebugStartable.kt
@@ -31,7 +31,7 @@
     dumpManager: DumpManager,
     private val commandRegistry: CommandRegistry,
     private val flagCommand: FlagCommand,
-    featureFlags: FeatureFlags
+    private val featureFlags: FeatureFlagsDebug
 ) : CoreStartable {
 
     init {
@@ -41,6 +41,7 @@
     }
 
     override fun start() {
+        featureFlags.init()
         commandRegistry.registerCommand(FlagCommand.FLAG_COMMAND) { flagCommand }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
index 40a8a1a..30cad5f 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FeatureFlagsRelease.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.flags;
 
+import static com.android.systemui.flags.FlagsCommonModule.ALL_FLAGS;
+
 import static java.util.Objects.requireNonNull;
 
 import android.content.res.Resources;
@@ -34,6 +36,7 @@
 import java.util.Map;
 
 import javax.inject.Inject;
+import javax.inject.Named;
 
 /**
  * Default implementation of the a Flag manager that returns default values for release builds
@@ -49,26 +52,47 @@
     private final SystemPropertiesHelper mSystemProperties;
     private final DeviceConfigProxy mDeviceConfigProxy;
     private final ServerFlagReader mServerFlagReader;
+    private final Restarter mRestarter;
+    private final Map<Integer, Flag<?>> mAllFlags;
     SparseBooleanArray mBooleanCache = new SparseBooleanArray();
     SparseArray<String> mStringCache = new SparseArray<>();
 
+    private final ServerFlagReader.ChangeListener mOnPropertiesChanged =
+            new ServerFlagReader.ChangeListener() {
+                @Override
+                public void onChange() {
+                    mRestarter.restart();
+                }
+            };
+
     @Inject
     public FeatureFlagsRelease(
             @Main Resources resources,
             SystemPropertiesHelper systemProperties,
             DeviceConfigProxy deviceConfigProxy,
-            ServerFlagReader serverFlagReader) {
+            ServerFlagReader serverFlagReader,
+            @Named(ALL_FLAGS) Map<Integer, Flag<?>> allFlags,
+            Restarter restarter) {
         mResources = resources;
         mSystemProperties = systemProperties;
         mDeviceConfigProxy = deviceConfigProxy;
         mServerFlagReader = serverFlagReader;
+        mAllFlags = allFlags;
+        mRestarter = restarter;
+    }
+
+    /** Call after construction to setup listeners. */
+    void init() {
+        mServerFlagReader.listenForChanges(mAllFlags.values(), mOnPropertiesChanged);
     }
 
     @Override
-    public void addListener(@NonNull Flag<?> flag, @NonNull Listener listener) {}
+    public void addListener(@NonNull Flag<?> flag, @NonNull Listener listener) {
+    }
 
     @Override
-    public void removeListener(@NonNull Listener listener) {}
+    public void removeListener(@NonNull Listener listener) {
+    }
 
     @Override
     public boolean isEnabled(@NotNull UnreleasedFlag flag) {
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java b/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java
index 4d25431..1e93c0b7 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagCommand.java
@@ -16,6 +16,8 @@
 
 package com.android.systemui.flags;
 
+import static com.android.systemui.flags.FlagsCommonModule.ALL_FLAGS;
+
 import androidx.annotation.NonNull;
 
 import com.android.systemui.statusbar.commandline.Command;
@@ -42,7 +44,7 @@
     @Inject
     FlagCommand(
             FeatureFlagsDebug featureFlags,
-            @Named(FeatureFlagsDebug.ALL_FLAGS) Map<Integer, Flag<?>> allFlags
+            @Named(ALL_FLAGS) Map<Integer, Flag<?>> allFlags
     ) {
         mFeatureFlags = featureFlags;
         mAllFlags = allFlags;
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 4818bcc..bcea262 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2022 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.
@@ -57,13 +57,13 @@
     val INSTANT_VOICE_REPLY = UnreleasedFlag(111, teamfood = true)
 
     // TODO(b/254512425): Tracking Bug
-    val NOTIFICATION_MEMORY_MONITOR_ENABLED = UnreleasedFlag(112, teamfood = true)
+    val NOTIFICATION_MEMORY_MONITOR_ENABLED = ReleasedFlag(112)
 
     // TODO(b/254512731): Tracking Bug
     @JvmField val NOTIFICATION_DISMISSAL_FADE = UnreleasedFlag(113, teamfood = true)
     val STABILITY_INDEX_FIX = UnreleasedFlag(114, teamfood = true)
     val SEMI_STABLE_SORT = UnreleasedFlag(115, teamfood = true)
-    @JvmField val NOTIFICATION_GROUP_CORNER = UnreleasedFlag(116, true)
+    @JvmField val NOTIFICATION_GROUP_CORNER = UnreleasedFlag(116, teamfood = true)
     // next id: 117
 
     // 200 - keyguard/lockscreen
@@ -89,7 +89,7 @@
      * replacement of KeyguardBouncer.java.
      */
     // TODO(b/254512385): Tracking Bug
-    @JvmField val MODERN_BOUNCER = UnreleasedFlag(208)
+    @JvmField val MODERN_BOUNCER = ReleasedFlag(208)
 
     /**
      * Whether the user interactor and repository should use `UserSwitcherController`.
@@ -122,7 +122,20 @@
      * Migration from the legacy isDozing/dozeAmount paths to the new KeyguardTransitionRepository
      * will occur in stages. This is one stage of many to come.
      */
-    @JvmField val DOZING_MIGRATION_1 = UnreleasedFlag(213, teamfood = true)
+    // TODO(b/255607168): Tracking Bug
+    @JvmField val DOZING_MIGRATION_1 = UnreleasedFlag(213)
+
+    @JvmField val NEW_ELLIPSE_DETECTION = UnreleasedFlag(214)
+
+    @JvmField val NEW_UDFPS_OVERLAY = UnreleasedFlag(215)
+
+    /**
+     * Whether to enable the code powering customizable lock screen quick affordances.
+     *
+     * Note that this flag does not enable individual implementations of quick affordances like the
+     * new camera quick affordance. Look for individual flags for those.
+     */
+    @JvmField val CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES = UnreleasedFlag(216, teamfood = false)
 
     // 300 - power menu
     // TODO(b/254512600): Tracking Bug
@@ -173,9 +186,17 @@
     @Deprecated("Replaced by mobile and wifi specific flags.")
     val NEW_STATUS_BAR_PIPELINE_FRONTEND = UnreleasedFlag(605, teamfood = false)
 
-    val NEW_STATUS_BAR_MOBILE_ICONS = UnreleasedFlag(606, false)
+    // TODO(b/256614753): Tracking Bug
+    val NEW_STATUS_BAR_MOBILE_ICONS = UnreleasedFlag(606)
 
-    val NEW_STATUS_BAR_WIFI_ICON = UnreleasedFlag(607, false)
+    // TODO(b/256614210): Tracking Bug
+    val NEW_STATUS_BAR_WIFI_ICON = UnreleasedFlag(607)
+
+    // TODO(b/256614751): Tracking Bug
+    val NEW_STATUS_BAR_MOBILE_ICONS_BACKEND = UnreleasedFlag(608)
+
+    // TODO(b/256613548): Tracking Bug
+    val NEW_STATUS_BAR_WIFI_ICON_BACKEND = UnreleasedFlag(609)
 
     // 700 - dialer/calls
     // TODO(b/254512734): Tracking Bug
@@ -196,7 +217,7 @@
 
     // 802 - wallpaper rendering
     // TODO(b/254512923): Tracking Bug
-    @JvmField val USE_CANVAS_RENDERER = ReleasedFlag(802)
+    @JvmField val USE_CANVAS_RENDERER = UnreleasedFlag(802, teamfood = true)
 
     // 803 - screen contents translation
     // TODO(b/254513187): Tracking Bug
@@ -225,26 +246,22 @@
     @JvmField val DREAM_MEDIA_TAP_TO_OPEN = UnreleasedFlag(906)
 
     // TODO(b/254513168): Tracking Bug
-    val UMO_SURFACE_RIPPLE = UnreleasedFlag(907)
+    @JvmField val UMO_SURFACE_RIPPLE = UnreleasedFlag(907)
 
     // 1000 - dock
     val SIMULATE_DOCK_THROUGH_CHARGING = ReleasedFlag(1000)
 
-    // TODO(b/254512444): Tracking Bug
-    @JvmField val DOCK_SETUP_ENABLED = ReleasedFlag(1001)
-
     // TODO(b/254512758): Tracking Bug
     @JvmField val ROUNDED_BOX_RIPPLE = ReleasedFlag(1002)
 
-    // TODO(b/254512525): Tracking Bug
-    @JvmField val REFACTORED_DOCK_SETUP = ReleasedFlag(1003, teamfood = true)
-
     // 1100 - windowing
+    @JvmField
     @Keep
     val WM_ENABLE_SHELL_TRANSITIONS =
         SysPropBooleanFlag(1100, "persist.wm.debug.shell_transit", false)
 
     /** b/170163464: animate bubbles expanded view collapse with home gesture */
+    @JvmField
     @Keep
     val BUBBLES_HOME_GESTURE =
         SysPropBooleanFlag(1101, "persist.wm.debug.bubbles_home_gesture", true)
@@ -266,40 +283,50 @@
     @Keep
     val HIDE_NAVBAR_WINDOW = SysPropBooleanFlag(1103, "persist.wm.debug.hide_navbar_window", false)
 
+    @JvmField
     @Keep
     val WM_DESKTOP_WINDOWING = SysPropBooleanFlag(1104, "persist.wm.debug.desktop_mode", false)
 
+    @JvmField
     @Keep
     val WM_CAPTION_ON_SHELL = SysPropBooleanFlag(1105, "persist.wm.debug.caption_on_shell", false)
 
+    @JvmField
     @Keep
     val FLOATING_TASKS_ENABLED = SysPropBooleanFlag(1106, "persist.wm.debug.floating_tasks", false)
 
+    @JvmField
     @Keep
     val SHOW_FLOATING_TASKS_AS_BUBBLES =
         SysPropBooleanFlag(1107, "persist.wm.debug.floating_tasks_as_bubbles", false)
 
+    @JvmField
     @Keep
     val ENABLE_FLING_TO_DISMISS_BUBBLE =
         SysPropBooleanFlag(1108, "persist.wm.debug.fling_to_dismiss_bubble", true)
 
+    @JvmField
     @Keep
     val ENABLE_FLING_TO_DISMISS_PIP =
         SysPropBooleanFlag(1109, "persist.wm.debug.fling_to_dismiss_pip", true)
 
+    @JvmField
     @Keep
     val ENABLE_PIP_KEEP_CLEAR_ALGORITHM =
         SysPropBooleanFlag(1110, "persist.wm.debug.enable_pip_keep_clear_algorithm", false)
 
     // 1200 - predictive back
+    @JvmField
     @Keep
     val WM_ENABLE_PREDICTIVE_BACK =
         SysPropBooleanFlag(1200, "persist.wm.debug.predictive_back", true)
 
+    @JvmField
     @Keep
     val WM_ENABLE_PREDICTIVE_BACK_ANIM =
         SysPropBooleanFlag(1201, "persist.wm.debug.predictive_back_anim", false)
 
+    @JvmField
     @Keep
     val WM_ALWAYS_ENFORCE_PREDICTIVE_BACK =
         SysPropBooleanFlag(1202, "persist.wm.debug.predictive_back_always_enforce", false)
@@ -309,7 +336,7 @@
 
     // 1300 - screenshots
     // TODO(b/254512719): Tracking Bug
-    @JvmField val SCREENSHOT_REQUEST_PROCESSOR = UnreleasedFlag(1300)
+    @JvmField val SCREENSHOT_REQUEST_PROCESSOR = UnreleasedFlag(1300, true)
 
     // TODO(b/254513155): Tracking Bug
     @JvmField val SCREENSHOT_WORK_PROFILE_POLICY = UnreleasedFlag(1301)
@@ -320,20 +347,24 @@
 
     // 1500 - chooser
     // TODO(b/254512507): Tracking Bug
-    val CHOOSER_UNBUNDLED = UnreleasedFlag(1500)
+    val CHOOSER_UNBUNDLED = UnreleasedFlag(1500, teamfood = true)
 
     // 1600 - accessibility
     @JvmField val A11Y_FLOATING_MENU_FLING_SPRING_ANIMATIONS = UnreleasedFlag(1600)
 
     // 1700 - clipboard
-    @JvmField val CLIPBOARD_OVERLAY_REFACTOR = UnreleasedFlag(1700)
+    @JvmField val CLIPBOARD_OVERLAY_REFACTOR = UnreleasedFlag(1700, true)
+    @JvmField val CLIPBOARD_REMOTE_BEHAVIOR = UnreleasedFlag(1701)
 
     // 1800 - shade container
-    @JvmField val LEAVE_SHADE_OPEN_FOR_BUGREPORT = UnreleasedFlag(1800, true)
+    @JvmField val LEAVE_SHADE_OPEN_FOR_BUGREPORT = UnreleasedFlag(1800, teamfood = true)
 
     // 1900 - note task
     @JvmField val NOTE_TASKS = SysPropBooleanFlag(1900, "persist.sysui.debug.note_tasks")
 
+    // 2000 - device controls
+    @Keep val USE_APP_PANELS = UnreleasedFlag(2000, true)
+
     // Pay no attention to the reflection behind the curtain.
     // ========================== Curtain ==========================
     // |                                                           |
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt
new file mode 100644
index 0000000..e1f4944
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagsCommonModule.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2022 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.flags
+
+import com.android.internal.statusbar.IStatusBarService
+import dagger.Module
+import dagger.Provides
+import javax.inject.Named
+
+/** Module containing shared code for all FeatureFlag implementations. */
+@Module
+interface FlagsCommonModule {
+    companion object {
+        const val ALL_FLAGS = "all_flags"
+
+        @JvmStatic
+        @Provides
+        @Named(ALL_FLAGS)
+        fun providesAllFlags(): Map<Int, Flag<*>> {
+            return Flags.collectFlags()
+        }
+
+        @JvmStatic
+        @Provides
+        fun providesRestarter(barService: IStatusBarService): Restarter {
+            return object : Restarter {
+                override fun restart() {
+                    barService.restart()
+                }
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt b/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt
index fc5b9f4..694fa01 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/ServerFlagReader.kt
@@ -16,9 +16,13 @@
 
 package com.android.systemui.flags
 
+import android.provider.DeviceConfig
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.util.DeviceConfigProxy
-import dagger.Binds
 import dagger.Module
+import dagger.Provides
+import java.util.concurrent.Executor
 import javax.inject.Inject
 
 interface ServerFlagReader {
@@ -27,40 +31,99 @@
 
     /** Returns any stored server-side setting or the default if not set. */
     fun readServerOverride(flagId: Int, default: Boolean): Boolean
+
+    /** Register a listener for changes to any of the passed in flags. */
+    fun listenForChanges(values: Collection<Flag<*>>, listener: ChangeListener)
+
+    interface ChangeListener {
+        fun onChange()
+    }
 }
 
 class ServerFlagReaderImpl @Inject constructor(
-    private val deviceConfig: DeviceConfigProxy
+    private val namespace: String,
+    private val deviceConfig: DeviceConfigProxy,
+    @Background private val executor: Executor
 ) : ServerFlagReader {
+
+    private val listeners =
+        mutableListOf<Pair<ServerFlagReader.ChangeListener, Collection<Flag<*>>>>()
+
+    private val onPropertiesChangedListener = object : DeviceConfig.OnPropertiesChangedListener {
+        override fun onPropertiesChanged(properties: DeviceConfig.Properties) {
+            if (properties.namespace != namespace) {
+                return
+            }
+
+            for ((listener, flags) in listeners) {
+                propLoop@ for (propName in properties.keyset) {
+                    for (flag in flags) {
+                        if (propName == getServerOverrideName(flag.id)) {
+                            listener.onChange()
+                            break@propLoop
+                        }
+                    }
+                }
+            }
+        }
+    }
+
     override fun hasOverride(flagId: Int): Boolean =
         deviceConfig.getProperty(
-            SYSUI_NAMESPACE,
+            namespace,
             getServerOverrideName(flagId)
         ) != null
 
     override fun readServerOverride(flagId: Int, default: Boolean): Boolean {
         return deviceConfig.getBoolean(
-            SYSUI_NAMESPACE,
+            namespace,
             getServerOverrideName(flagId),
             default
         )
     }
 
+    override fun listenForChanges(
+        flags: Collection<Flag<*>>,
+        listener: ServerFlagReader.ChangeListener
+    ) {
+        if (listeners.isEmpty()) {
+            deviceConfig.addOnPropertiesChangedListener(
+                namespace,
+                executor,
+                onPropertiesChangedListener
+            )
+        }
+        listeners.add(Pair(listener, flags))
+    }
+
     private fun getServerOverrideName(flagId: Int): String {
         return "flag_override_$flagId"
     }
 }
 
-private val SYSUI_NAMESPACE = "systemui"
-
 @Module
 interface ServerFlagReaderModule {
-    @Binds
-    fun bindsReader(impl: ServerFlagReaderImpl): ServerFlagReader
+    companion object {
+        private val SYSUI_NAMESPACE = "systemui"
+
+        @JvmStatic
+        @Provides
+        @SysUISingleton
+        fun bindsReader(
+            deviceConfig: DeviceConfigProxy,
+            @Background executor: Executor
+        ): ServerFlagReader {
+            return ServerFlagReaderImpl(
+                SYSUI_NAMESPACE, deviceConfig, executor
+            )
+        }
+    }
 }
 
 class ServerFlagReaderFake : ServerFlagReader {
     private val flagMap: MutableMap<Int, Boolean> = mutableMapOf()
+    private val listeners =
+        mutableListOf<Pair<ServerFlagReader.ChangeListener, Collection<Flag<*>>>>()
 
     override fun hasOverride(flagId: Int): Boolean {
         return flagMap.containsKey(flagId)
@@ -72,9 +135,24 @@
 
     fun setFlagValue(flagId: Int, value: Boolean) {
         flagMap.put(flagId, value)
+
+        for ((listener, flags) in listeners) {
+            flagLoop@ for (flag in flags) {
+                if (flagId == flag.id) {
+                    listener.onChange()
+                    break@flagLoop
+                }
+            }
+        }
     }
 
     fun eraseFlag(flagId: Int) {
         flagMap.remove(flagId)
     }
+
+    override fun listenForChanges(
+        flags: Collection<Flag<*>>,
+        listener: ServerFlagReader.ChangeListener
+    ) {
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java
index 546a409..450fa14 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/WorkLockActivity.java
@@ -33,6 +33,8 @@
 import android.os.UserHandle;
 import android.os.UserManager;
 import android.widget.ImageView;
+import android.window.OnBackInvokedCallback;
+import android.window.OnBackInvokedDispatcher;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.R;
@@ -61,6 +63,7 @@
     private UserManager mUserManager;
     private PackageManager mPackageManager;
     private final BroadcastDispatcher mBroadcastDispatcher;
+    private final OnBackInvokedCallback mBackCallback = this::onBackInvoked;
 
     @Inject
     public WorkLockActivity(BroadcastDispatcher broadcastDispatcher, UserManager userManager,
@@ -95,6 +98,10 @@
         if (badgedIcon != null) {
             ((ImageView) findViewById(R.id.icon)).setImageDrawable(badgedIcon);
         }
+
+        getOnBackInvokedDispatcher().registerOnBackInvokedCallback(
+                OnBackInvokedDispatcher.PRIORITY_DEFAULT,
+                mBackCallback);
     }
 
     @VisibleForTesting
@@ -134,11 +141,16 @@
     @Override
     public void onDestroy() {
         unregisterBroadcastReceiver();
+        getOnBackInvokedDispatcher().unregisterOnBackInvokedCallback(mBackCallback);
         super.onDestroy();
     }
 
     @Override
     public void onBackPressed() {
+        onBackInvoked();
+    }
+
+    private void onBackInvoked() {
         // Ignore back presses.
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 56a1f1a..f08463b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -42,6 +42,7 @@
 import com.android.systemui.keyguard.DismissCallbackRegistry;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
 import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardDataQuickAffordanceModule;
 import com.android.systemui.keyguard.data.repository.KeyguardRepositoryModule;
 import com.android.systemui.keyguard.domain.interactor.StartKeyguardTransitionModule;
 import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceModule;
@@ -71,6 +72,7 @@
         KeyguardUserSwitcherComponent.class},
         includes = {
             FalsingModule.class,
+            KeyguardDataQuickAffordanceModule.class,
             KeyguardQuickAffordanceModule.class,
             KeyguardRepositoryModule.class,
             StartKeyguardTransitionModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt
index 99ae85d..80c6130 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/BouncerView.kt
@@ -18,6 +18,7 @@
 
 import android.view.KeyEvent
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.ActivityStarter
 import java.lang.ref.WeakReference
 import javax.inject.Inject
 
@@ -45,4 +46,9 @@
     fun dispatchBackKeyEventPreIme(): Boolean
     fun showNextSecurityScreenOrFinish(): Boolean
     fun resume()
+    fun setDismissAction(
+        onDismissAction: ActivityStarter.OnDismissAction?,
+        cancelAction: Runnable?,
+    )
+    fun willDismissWithActions(): Boolean
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
index d3bb34c..d6f521c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfig.kt
@@ -53,19 +53,23 @@
 
     override val key: String = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
 
-    override val state: Flow<KeyguardQuickAffordanceConfig.State> =
+    override val pickerName: String by lazy { context.getString(component.getTileTitleId()) }
+
+    override val pickerIconResourceId: Int by lazy { component.getTileImageId() }
+
+    override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
         component.canShowWhileLockedSetting.flatMapLatest { canShowWhileLocked ->
             if (canShowWhileLocked) {
                 stateInternal(component.getControlsListingController().getOrNull())
             } else {
-                flowOf(KeyguardQuickAffordanceConfig.State.Hidden)
+                flowOf(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
             }
         }
 
-    override fun onQuickAffordanceClicked(
+    override fun onTriggered(
         expandable: Expandable?,
-    ): KeyguardQuickAffordanceConfig.OnClickedResult {
-        return KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity(
+    ): KeyguardQuickAffordanceConfig.OnTriggeredResult {
+        return KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity(
             intent =
                 Intent(appContext, ControlsActivity::class.java)
                     .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
@@ -79,9 +83,9 @@
 
     private fun stateInternal(
         listingController: ControlsListingController?,
-    ): Flow<KeyguardQuickAffordanceConfig.State> {
+    ): Flow<KeyguardQuickAffordanceConfig.LockScreenState> {
         if (listingController == null) {
-            return flowOf(KeyguardQuickAffordanceConfig.State.Hidden)
+            return flowOf(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
         }
 
         return conflatedCallbackFlow {
@@ -116,7 +120,7 @@
         hasServiceInfos: Boolean,
         visibility: ControlsComponent.Visibility,
         @DrawableRes iconResourceId: Int?,
-    ): KeyguardQuickAffordanceConfig.State {
+    ): KeyguardQuickAffordanceConfig.LockScreenState {
         return if (
             isFeatureEnabled &&
                 hasFavorites &&
@@ -124,7 +128,7 @@
                 iconResourceId != null &&
                 visibility == ControlsComponent.Visibility.AVAILABLE
         ) {
-            KeyguardQuickAffordanceConfig.State.Visible(
+            KeyguardQuickAffordanceConfig.LockScreenState.Visible(
                 icon =
                     Icon.Resource(
                         res = iconResourceId,
@@ -135,7 +139,7 @@
                     ),
             )
         } else {
-            KeyguardQuickAffordanceConfig.State.Hidden
+            KeyguardQuickAffordanceConfig.LockScreenState.Hidden
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt
new file mode 100644
index 0000000..bea9363
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardDataQuickAffordanceModule.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 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.keyguard.data.quickaffordance
+
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.ElementsIntoSet
+
+@Module
+object KeyguardDataQuickAffordanceModule {
+    @Provides
+    @ElementsIntoSet
+    fun quickAffordanceConfigs(
+        home: HomeControlsKeyguardQuickAffordanceConfig,
+        quickAccessWallet: QuickAccessWalletKeyguardQuickAffordanceConfig,
+        qrCodeScanner: QrCodeScannerKeyguardQuickAffordanceConfig,
+    ): Set<KeyguardQuickAffordanceConfig> {
+        return setOf(
+            home,
+            quickAccessWallet,
+            qrCodeScanner,
+        )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
index 0dd0ad7..fd40d1d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceConfig.kt
@@ -20,7 +20,7 @@
 import android.content.Intent
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordanceToggleState
+import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
 import kotlinx.coroutines.flow.Flow
 
 /** Defines interface that can act as data source for a single quick affordance model. */
@@ -29,51 +29,58 @@
     /** Unique identifier for this quick affordance. It must be globally unique. */
     val key: String
 
-    /** The observable [State] of the affordance. */
-    val state: Flow<State>
+    val pickerName: String
+
+    val pickerIconResourceId: Int
+
+    /**
+     * The ever-changing state of the affordance.
+     *
+     * Used to populate the lock screen.
+     */
+    val lockScreenState: Flow<LockScreenState>
 
     /**
      * Notifies that the affordance was clicked by the user.
      *
      * @param expandable An [Expandable] to use when animating dialogs or activities
-     * @return An [OnClickedResult] telling the caller what to do next
+     * @return An [OnTriggeredResult] telling the caller what to do next
      */
-    fun onQuickAffordanceClicked(expandable: Expandable?): OnClickedResult
+    fun onTriggered(expandable: Expandable?): OnTriggeredResult
 
     /**
      * Encapsulates the state of a "quick affordance" in the keyguard bottom area (for example, a
      * button on the lock-screen).
      */
-    sealed class State {
+    sealed class LockScreenState {
 
         /** No affordance should show up. */
-        object Hidden : State()
+        object Hidden : LockScreenState()
 
         /** An affordance is visible. */
         data class Visible(
             /** An icon for the affordance. */
             val icon: Icon,
-            /** The toggle state for the affordance. */
-            val toggle: KeyguardQuickAffordanceToggleState =
-                KeyguardQuickAffordanceToggleState.NotSupported,
-        ) : State()
+            /** The activation state of the affordance. */
+            val activationState: ActivationState = ActivationState.NotSupported,
+        ) : LockScreenState()
     }
 
-    sealed class OnClickedResult {
+    sealed class OnTriggeredResult {
         /**
-         * Returning this as a result from the [onQuickAffordanceClicked] method means that the
-         * implementation has taken care of the click, the system will do nothing.
+         * Returning this as a result from the [onTriggered] method means that the implementation
+         * has taken care of the action, the system will do nothing.
          */
-        object Handled : OnClickedResult()
+        object Handled : OnTriggeredResult()
 
         /**
-         * Returning this as a result from the [onQuickAffordanceClicked] method means that the
-         * implementation has _not_ taken care of the click and the system should start an activity
-         * using the given [Intent].
+         * Returning this as a result from the [onTriggered] method means that the implementation
+         * has _not_ taken care of the action and the system should start an activity using the
+         * given [Intent].
          */
         data class StartActivity(
             val intent: Intent,
             val canShowWhileLocked: Boolean,
-        ) : OnClickedResult()
+        ) : OnTriggeredResult()
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt
new file mode 100644
index 0000000..9c9354f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManager.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright (C) 2022 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.keyguard.data.quickaffordance
+
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/**
+ * Manages and provides access to the current "selections" of keyguard quick affordances, answering
+ * the question "which affordances should the keyguard show?".
+ */
+@SysUISingleton
+class KeyguardQuickAffordanceSelectionManager @Inject constructor() {
+
+    // TODO(b/254858695): implement a persistence layer (database).
+    private val _selections = MutableStateFlow<Map<String, List<String>>>(emptyMap())
+
+    /** IDs of affordances to show, indexed by slot ID, and sorted in descending priority order. */
+    val selections: Flow<Map<String, List<String>>> = _selections.asStateFlow()
+
+    /**
+     * Returns a snapshot of the IDs of affordances to show, indexed by slot ID, and sorted in
+     * descending priority order.
+     */
+    suspend fun getSelections(): Map<String, List<String>> {
+        return _selections.value
+    }
+
+    /**
+     * Updates the IDs of affordances to show at the slot with the given ID. The order of affordance
+     * IDs should be descending priority order.
+     */
+    suspend fun setSelections(
+        slotId: String,
+        affordanceIds: List<String>,
+    ) {
+        // Must make a copy of the map and update it, otherwise, the MutableStateFlow won't emit
+        // when we set its value to the same instance of the original map, even if we change the
+        // map by updating the value of one of its keys.
+        val copy = _selections.value.toMutableMap()
+        copy[slotId] = affordanceIds
+        _selections.value = copy
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
index 9a44139..11f72ff 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfig.kt
@@ -17,6 +17,7 @@
 
 package com.android.systemui.keyguard.data.quickaffordance
 
+import android.content.Context
 import com.android.systemui.R
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
@@ -24,6 +25,7 @@
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.qrcodescanner.controller.QRCodeScannerController
 import javax.inject.Inject
 import kotlinx.coroutines.channels.awaitClose
@@ -34,51 +36,57 @@
 class QrCodeScannerKeyguardQuickAffordanceConfig
 @Inject
 constructor(
+    @Application context: Context,
     private val controller: QRCodeScannerController,
 ) : KeyguardQuickAffordanceConfig {
 
     override val key: String = BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER
 
-    override val state: Flow<KeyguardQuickAffordanceConfig.State> = conflatedCallbackFlow {
-        val callback =
-            object : QRCodeScannerController.Callback {
-                override fun onQRCodeScannerActivityChanged() {
-                    trySendWithFailureLogging(state(), TAG)
-                }
-                override fun onQRCodeScannerPreferenceChanged() {
-                    trySendWithFailureLogging(state(), TAG)
-                }
-            }
+    override val pickerName = context.getString(R.string.qr_code_scanner_title)
 
-        controller.addCallback(callback)
-        controller.registerQRCodeScannerChangeObservers(
-            QRCodeScannerController.DEFAULT_QR_CODE_SCANNER_CHANGE,
-            QRCodeScannerController.QR_CODE_SCANNER_PREFERENCE_CHANGE
-        )
-        // Registering does not push an initial update.
-        trySendWithFailureLogging(state(), "initial state", TAG)
+    override val pickerIconResourceId = R.drawable.ic_qr_code_scanner
 
-        awaitClose {
-            controller.unregisterQRCodeScannerChangeObservers(
+    override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
+        conflatedCallbackFlow {
+            val callback =
+                object : QRCodeScannerController.Callback {
+                    override fun onQRCodeScannerActivityChanged() {
+                        trySendWithFailureLogging(state(), TAG)
+                    }
+                    override fun onQRCodeScannerPreferenceChanged() {
+                        trySendWithFailureLogging(state(), TAG)
+                    }
+                }
+
+            controller.addCallback(callback)
+            controller.registerQRCodeScannerChangeObservers(
                 QRCodeScannerController.DEFAULT_QR_CODE_SCANNER_CHANGE,
                 QRCodeScannerController.QR_CODE_SCANNER_PREFERENCE_CHANGE
             )
-            controller.removeCallback(callback)
-        }
-    }
+            // Registering does not push an initial update.
+            trySendWithFailureLogging(state(), "initial state", TAG)
 
-    override fun onQuickAffordanceClicked(
+            awaitClose {
+                controller.unregisterQRCodeScannerChangeObservers(
+                    QRCodeScannerController.DEFAULT_QR_CODE_SCANNER_CHANGE,
+                    QRCodeScannerController.QR_CODE_SCANNER_PREFERENCE_CHANGE
+                )
+                controller.removeCallback(callback)
+            }
+        }
+
+    override fun onTriggered(
         expandable: Expandable?,
-    ): KeyguardQuickAffordanceConfig.OnClickedResult {
-        return KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity(
+    ): KeyguardQuickAffordanceConfig.OnTriggeredResult {
+        return KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity(
             intent = controller.intent,
             canShowWhileLocked = true,
         )
     }
 
-    private fun state(): KeyguardQuickAffordanceConfig.State {
+    private fun state(): KeyguardQuickAffordanceConfig.LockScreenState {
         return if (controller.isEnabledForLockScreenButton) {
-            KeyguardQuickAffordanceConfig.State.Visible(
+            KeyguardQuickAffordanceConfig.LockScreenState.Visible(
                 icon =
                     Icon.Resource(
                         res = R.drawable.ic_qr_code_scanner,
@@ -89,7 +97,7 @@
                     ),
             )
         } else {
-            KeyguardQuickAffordanceConfig.State.Hidden
+            KeyguardQuickAffordanceConfig.LockScreenState.Hidden
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
index 8a1267e..303e6a1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfig.kt
@@ -17,6 +17,7 @@
 
 package com.android.systemui.keyguard.data.quickaffordance
 
+import android.content.Context
 import android.graphics.drawable.Drawable
 import android.service.quickaccesswallet.GetWalletCardsError
 import android.service.quickaccesswallet.GetWalletCardsResponse
@@ -29,6 +30,7 @@
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.wallet.controller.QuickAccessWalletController
 import javax.inject.Inject
@@ -40,69 +42,75 @@
 class QuickAccessWalletKeyguardQuickAffordanceConfig
 @Inject
 constructor(
+    @Application context: Context,
     private val walletController: QuickAccessWalletController,
     private val activityStarter: ActivityStarter,
 ) : KeyguardQuickAffordanceConfig {
 
     override val key: String = BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
 
-    override val state: Flow<KeyguardQuickAffordanceConfig.State> = conflatedCallbackFlow {
-        val callback =
-            object : QuickAccessWalletClient.OnWalletCardsRetrievedCallback {
-                override fun onWalletCardsRetrieved(response: GetWalletCardsResponse?) {
-                    trySendWithFailureLogging(
-                        state(
-                            isFeatureEnabled = walletController.isWalletEnabled,
-                            hasCard = response?.walletCards?.isNotEmpty() == true,
-                            tileIcon = walletController.walletClient.tileIcon,
-                        ),
-                        TAG,
-                    )
+    override val pickerName = context.getString(R.string.accessibility_wallet_button)
+
+    override val pickerIconResourceId = R.drawable.ic_wallet_lockscreen
+
+    override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
+        conflatedCallbackFlow {
+            val callback =
+                object : QuickAccessWalletClient.OnWalletCardsRetrievedCallback {
+                    override fun onWalletCardsRetrieved(response: GetWalletCardsResponse?) {
+                        trySendWithFailureLogging(
+                            state(
+                                isFeatureEnabled = walletController.isWalletEnabled,
+                                hasCard = response?.walletCards?.isNotEmpty() == true,
+                                tileIcon = walletController.walletClient.tileIcon,
+                            ),
+                            TAG,
+                        )
+                    }
+
+                    override fun onWalletCardRetrievalError(error: GetWalletCardsError?) {
+                        Log.e(TAG, "Wallet card retrieval error, message: \"${error?.message}\"")
+                        trySendWithFailureLogging(
+                            KeyguardQuickAffordanceConfig.LockScreenState.Hidden,
+                            TAG,
+                        )
+                    }
                 }
 
-                override fun onWalletCardRetrievalError(error: GetWalletCardsError?) {
-                    Log.e(TAG, "Wallet card retrieval error, message: \"${error?.message}\"")
-                    trySendWithFailureLogging(
-                        KeyguardQuickAffordanceConfig.State.Hidden,
-                        TAG,
-                    )
-                }
-            }
-
-        walletController.setupWalletChangeObservers(
-            callback,
-            QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE,
-            QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE
-        )
-        walletController.updateWalletPreference()
-        walletController.queryWalletCards(callback)
-
-        awaitClose {
-            walletController.unregisterWalletChangeObservers(
+            walletController.setupWalletChangeObservers(
+                callback,
                 QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE,
                 QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE
             )
-        }
-    }
+            walletController.updateWalletPreference()
+            walletController.queryWalletCards(callback)
 
-    override fun onQuickAffordanceClicked(
+            awaitClose {
+                walletController.unregisterWalletChangeObservers(
+                    QuickAccessWalletController.WalletChangeEvent.WALLET_PREFERENCE_CHANGE,
+                    QuickAccessWalletController.WalletChangeEvent.DEFAULT_PAYMENT_APP_CHANGE
+                )
+            }
+        }
+
+    override fun onTriggered(
         expandable: Expandable?,
-    ): KeyguardQuickAffordanceConfig.OnClickedResult {
+    ): KeyguardQuickAffordanceConfig.OnTriggeredResult {
         walletController.startQuickAccessUiIntent(
             activityStarter,
             expandable?.activityLaunchController(),
             /* hasCard= */ true,
         )
-        return KeyguardQuickAffordanceConfig.OnClickedResult.Handled
+        return KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
     }
 
     private fun state(
         isFeatureEnabled: Boolean,
         hasCard: Boolean,
         tileIcon: Drawable?,
-    ): KeyguardQuickAffordanceConfig.State {
+    ): KeyguardQuickAffordanceConfig.LockScreenState {
         return if (isFeatureEnabled && hasCard && tileIcon != null) {
-            KeyguardQuickAffordanceConfig.State.Visible(
+            KeyguardQuickAffordanceConfig.LockScreenState.Visible(
                 icon =
                     Icon.Loaded(
                         drawable = tileIcon,
@@ -113,7 +121,7 @@
                     ),
             )
         } else {
-            KeyguardQuickAffordanceConfig.State.Hidden
+            KeyguardQuickAffordanceConfig.LockScreenState.Hidden
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
index 543389e..0046256 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBouncerRepository.kt
@@ -21,10 +21,9 @@
 import com.android.keyguard.KeyguardUpdateMonitorCallback
 import com.android.keyguard.ViewMediatorCallback
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.shared.model.BouncerCallbackActionsModel
 import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
 import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
-import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_HIDDEN
+import com.android.systemui.statusbar.phone.KeyguardBouncer
 import javax.inject.Inject
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
@@ -41,9 +40,15 @@
     /** Determines if we want to instantaneously show the bouncer instead of translating. */
     private val _isScrimmed = MutableStateFlow(false)
     val isScrimmed = _isScrimmed.asStateFlow()
-    /** Set amount of how much of the bouncer is showing on the screen */
-    private val _expansionAmount = MutableStateFlow(EXPANSION_HIDDEN)
-    val expansionAmount = _expansionAmount.asStateFlow()
+    /**
+     * Set how much of the panel is showing on the screen.
+     * ```
+     *      0f = panel fully hidden = bouncer fully showing
+     *      1f = panel fully showing = bouncer fully hidden
+     * ```
+     */
+    private val _panelExpansionAmount = MutableStateFlow(KeyguardBouncer.EXPANSION_HIDDEN)
+    val panelExpansionAmount = _panelExpansionAmount.asStateFlow()
     private val _isVisible = MutableStateFlow(false)
     val isVisible = _isVisible.asStateFlow()
     private val _show = MutableStateFlow<KeyguardBouncerModel?>(null)
@@ -54,8 +59,6 @@
     val hide = _hide.asStateFlow()
     private val _startingToHide = MutableStateFlow(false)
     val startingToHide = _startingToHide.asStateFlow()
-    private val _onDismissAction = MutableStateFlow<BouncerCallbackActionsModel?>(null)
-    val onDismissAction = _onDismissAction.asStateFlow()
     private val _disappearAnimation = MutableStateFlow<Runnable?>(null)
     val startingDisappearAnimation = _disappearAnimation.asStateFlow()
     private val _keyguardPosition = MutableStateFlow(0f)
@@ -96,8 +99,8 @@
         _isScrimmed.value = isScrimmed
     }
 
-    fun setExpansion(expansion: Float) {
-        _expansionAmount.value = expansion
+    fun setPanelExpansion(panelExpansion: Float) {
+        _panelExpansionAmount.value = panelExpansion
     }
 
     fun setVisible(isVisible: Boolean) {
@@ -120,10 +123,6 @@
         _startingToHide.value = startingToHide
     }
 
-    fun setOnDismissAction(bouncerCallbackActionsModel: BouncerCallbackActionsModel?) {
-        _onDismissAction.value = bouncerCallbackActionsModel
-    }
-
     fun setStartDisappearAnimation(runnable: Runnable?) {
         _disappearAnimation.value = runnable
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
new file mode 100644
index 0000000..95f614f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2022 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.keyguard.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation
+import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+
+/** Abstracts access to application state related to keyguard quick affordances. */
+@SysUISingleton
+class KeyguardQuickAffordanceRepository
+@Inject
+constructor(
+    @Application private val scope: CoroutineScope,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
+    private val selectionManager: KeyguardQuickAffordanceSelectionManager,
+    private val configs: Set<@JvmSuppressWildcards KeyguardQuickAffordanceConfig>,
+) {
+    /**
+     * List of [KeyguardQuickAffordanceConfig] instances of the affordances at the slot with the
+     * given ID. The configs are sorted in descending priority order.
+     */
+    val selections: StateFlow<Map<String, List<KeyguardQuickAffordanceConfig>>> =
+        selectionManager.selections
+            .map { selectionsBySlotId ->
+                selectionsBySlotId.mapValues { (_, selections) ->
+                    configs.filter { selections.contains(it.key) }
+                }
+            }
+            .stateIn(
+                scope = scope,
+                started = SharingStarted.Eagerly,
+                initialValue = emptyMap(),
+            )
+
+    /**
+     * Returns a snapshot of the [KeyguardQuickAffordanceConfig] instances of the affordances at the
+     * slot with the given ID. The configs are sorted in descending priority order.
+     */
+    suspend fun getSelections(slotId: String): List<KeyguardQuickAffordanceConfig> {
+        val selections = selectionManager.getSelections().getOrDefault(slotId, emptyList())
+        return configs.filter { selections.contains(it.key) }
+    }
+
+    /**
+     * Returns a snapshot of the IDs of the selected affordances, indexed by slot ID. The configs
+     * are sorted in descending priority order.
+     */
+    suspend fun getSelections(): Map<String, List<String>> {
+        return selectionManager.getSelections()
+    }
+
+    /**
+     * Updates the IDs of affordances to show at the slot with the given ID. The order of affordance
+     * IDs should be descending priority order.
+     */
+    fun setSelections(
+        slotId: String,
+        affordanceIds: List<String>,
+    ) {
+        scope.launch(backgroundDispatcher) {
+            selectionManager.setSelections(
+                slotId = slotId,
+                affordanceIds = affordanceIds,
+            )
+        }
+    }
+
+    /**
+     * Returns the list of representation objects for all known affordances, regardless of what is
+     * selected. This is useful for building experiences like the picker/selector or user settings
+     * so the user can see everything that can be selected in a menu.
+     */
+    fun getAffordancePickerRepresentations(): List<KeyguardQuickAffordancePickerRepresentation> {
+        return configs.map { config ->
+            KeyguardQuickAffordancePickerRepresentation(
+                id = config.key,
+                name = config.pickerName,
+                iconResourceId = config.pickerIconResourceId,
+            )
+        }
+    }
+
+    /**
+     * Returns the list of representation objects for all available slots on the keyguard. This is
+     * useful for building experiences like the picker/selector or user settings so the user can see
+     * each slot and select which affordance(s) is/are installed in each slot on the keyguard.
+     */
+    fun getSlotPickerRepresentations(): List<KeyguardSlotPickerRepresentation> {
+        // TODO(b/256195304): source these from a config XML file.
+        return listOf(
+            KeyguardSlotPickerRepresentation(
+                id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+            ),
+            KeyguardSlotPickerRepresentation(
+                id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+            ),
+        )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index b186ae0..c867c6e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -21,8 +21,14 @@
 import com.android.systemui.common.shared.model.Position
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.doze.DozeHost
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.keyguard.WakefulnessLifecycle.Wakefulness
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.keyguard.shared.model.WakefulnessModel
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.phone.BiometricUnlockController
+import com.android.systemui.statusbar.phone.BiometricUnlockController.WakeAndUnlockMode
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import javax.inject.Inject
 import kotlinx.coroutines.channels.awaitClose
@@ -62,6 +68,9 @@
      */
     val isKeyguardShowing: Flow<Boolean>
 
+    /** Observable for whether the bouncer is showing. */
+    val isBouncerShowing: Flow<Boolean>
+
     /**
      * Observable for whether we are in doze state.
      *
@@ -89,6 +98,12 @@
     /** Observable for the [StatusBarState] */
     val statusBarState: Flow<StatusBarState>
 
+    /** Observable for device wake/sleep state */
+    val wakefulnessState: Flow<WakefulnessModel>
+
+    /** Observable for biometric unlock modes */
+    val biometricUnlockState: Flow<BiometricUnlockModel>
+
     /**
      * Returns `true` if the keyguard is showing; `false` otherwise.
      *
@@ -118,6 +133,8 @@
     statusBarStateController: StatusBarStateController,
     private val keyguardStateController: KeyguardStateController,
     dozeHost: DozeHost,
+    wakefulnessLifecycle: WakefulnessLifecycle,
+    biometricUnlockController: BiometricUnlockController,
 ) : KeyguardRepository {
     private val _animateBottomAreaDozingTransitions = MutableStateFlow(false)
     override val animateBottomAreaDozingTransitions =
@@ -152,6 +169,29 @@
         awaitClose { keyguardStateController.removeCallback(callback) }
     }
 
+    override val isBouncerShowing: Flow<Boolean> = conflatedCallbackFlow {
+        val callback =
+            object : KeyguardStateController.Callback {
+                override fun onBouncerShowingChanged() {
+                    trySendWithFailureLogging(
+                        keyguardStateController.isBouncerShowing,
+                        TAG,
+                        "updated isBouncerShowing"
+                    )
+                }
+            }
+
+        keyguardStateController.addCallback(callback)
+        // Adding the callback does not send an initial update.
+        trySendWithFailureLogging(
+            keyguardStateController.isBouncerShowing,
+            TAG,
+            "initial isBouncerShowing"
+        )
+
+        awaitClose { keyguardStateController.removeCallback(callback) }
+    }
+
     override val isDozing: Flow<Boolean> =
         conflatedCallbackFlow {
                 val callback =
@@ -207,6 +247,58 @@
         awaitClose { statusBarStateController.removeCallback(callback) }
     }
 
+    override val wakefulnessState: Flow<WakefulnessModel> = conflatedCallbackFlow {
+        val callback =
+            object : WakefulnessLifecycle.Observer {
+                override fun onStartedWakingUp() {
+                    trySendWithFailureLogging(
+                        WakefulnessModel.STARTING_TO_WAKE,
+                        TAG,
+                        "Wakefulness: starting to wake"
+                    )
+                }
+                override fun onFinishedWakingUp() {
+                    trySendWithFailureLogging(WakefulnessModel.AWAKE, TAG, "Wakefulness: awake")
+                }
+                override fun onStartedGoingToSleep() {
+                    trySendWithFailureLogging(
+                        WakefulnessModel.STARTING_TO_SLEEP,
+                        TAG,
+                        "Wakefulness: starting to sleep"
+                    )
+                }
+                override fun onFinishedGoingToSleep() {
+                    trySendWithFailureLogging(WakefulnessModel.ASLEEP, TAG, "Wakefulness: asleep")
+                }
+            }
+        wakefulnessLifecycle.addObserver(callback)
+        trySendWithFailureLogging(
+            wakefulnessIntToObject(wakefulnessLifecycle.getWakefulness()),
+            TAG,
+            "initial wakefulness state"
+        )
+
+        awaitClose { wakefulnessLifecycle.removeObserver(callback) }
+    }
+
+    override val biometricUnlockState: Flow<BiometricUnlockModel> = conflatedCallbackFlow {
+        val callback =
+            object : BiometricUnlockController.BiometricModeListener {
+                override fun onModeChanged(@WakeAndUnlockMode mode: Int) {
+                    trySendWithFailureLogging(biometricModeIntToObject(mode), TAG, "biometric mode")
+                }
+            }
+
+        biometricUnlockController.addBiometricModeListener(callback)
+        trySendWithFailureLogging(
+            biometricModeIntToObject(biometricUnlockController.getMode()),
+            TAG,
+            "initial biometric mode"
+        )
+
+        awaitClose { biometricUnlockController.removeBiometricModeListener(callback) }
+    }
+
     override fun setAnimateDozingTransitions(animate: Boolean) {
         _animateBottomAreaDozingTransitions.value = animate
     }
@@ -228,6 +320,30 @@
         }
     }
 
+    private fun wakefulnessIntToObject(@Wakefulness value: Int): WakefulnessModel {
+        return when (value) {
+            0 -> WakefulnessModel.ASLEEP
+            1 -> WakefulnessModel.STARTING_TO_WAKE
+            2 -> WakefulnessModel.AWAKE
+            3 -> WakefulnessModel.STARTING_TO_SLEEP
+            else -> throw IllegalArgumentException("Invalid Wakefulness value: $value")
+        }
+    }
+
+    private fun biometricModeIntToObject(@WakeAndUnlockMode value: Int): BiometricUnlockModel {
+        return when (value) {
+            0 -> BiometricUnlockModel.NONE
+            1 -> BiometricUnlockModel.WAKE_AND_UNLOCK
+            2 -> BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING
+            3 -> BiometricUnlockModel.SHOW_BOUNCER
+            4 -> BiometricUnlockModel.ONLY_WAKE
+            5 -> BiometricUnlockModel.UNLOCK_COLLAPSING
+            6 -> BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM
+            7 -> BiometricUnlockModel.DISMISS_BOUNCER
+            else -> throw IllegalArgumentException("Invalid BiometricUnlockModel value: $value")
+        }
+    }
+
     companion object {
         private const val TAG = "KeyguardRepositoryImpl"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
index d15d7f2..0c72520 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryModule.kt
@@ -22,4 +22,9 @@
 @Module
 interface KeyguardRepositoryModule {
     @Binds fun keyguardRepository(impl: KeyguardRepositoryImpl): KeyguardRepository
+
+    @Binds
+    fun keyguardTransitionRepository(
+        impl: KeyguardTransitionRepositoryImpl
+    ): KeyguardTransitionRepository
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
index ab25597..e3d1a27 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepository.kt
@@ -29,27 +29,33 @@
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import java.util.UUID
 import javax.inject.Inject
+import kotlinx.coroutines.channels.BufferOverflow
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.asSharedFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filter
 
-@SysUISingleton
-class KeyguardTransitionRepository @Inject constructor() {
-    /*
-     * Each transition between [KeyguardState]s will have an associated Flow.
-     * In order to collect these events, clients should call [transition].
+/**
+ * The source of truth for all keyguard transitions.
+ *
+ * While the keyguard component is visible, it can undergo a number of transitions between different
+ * UI screens, such as AOD (Always-on Display), Bouncer, and others mentioned in [KeyguardState].
+ * These UI elements should listen to events emitted by [transitions], to ensure a centrally
+ * coordinated experience.
+ *
+ * To create or modify logic that controls when and how transitions get created, look at
+ * [TransitionInteractor]. These interactors will call [startTransition] and [updateTransition] on
+ * this repository.
+ */
+interface KeyguardTransitionRepository {
+    /**
+     * All events regarding transitions, as they start, run, and complete. [TransitionStep#value] is
+     * a float between [0, 1] representing progress towards completion. If this is a user driven
+     * transition, that value may not be a monotonic progression, as the user may swipe in any
+     * direction.
      */
-    private val _transitions = MutableStateFlow(TransitionStep())
-    val transitions = _transitions.asStateFlow()
-
-    /* Information about the active transition. */
-    private var currentTransitionInfo: TransitionInfo? = null
-    /*
-     * When manual control of the transition is requested, a unique [UUID] is used as the handle
-     * to permit calls to [updateTransition]
-     */
-    private var updateTransitionId: UUID? = null
+    val transitions: Flow<TransitionStep>
 
     /**
      * Interactors that require information about changes between [KeyguardState]s will call this to
@@ -60,65 +66,10 @@
     }
 
     /**
-     * Begin a transition from one state to another. The [info.from] must match
-     * [currentTransitionInfo.to], or the request will be denied. This is enforced to avoid
-     * unplanned transitions.
+     * Begin a transition from one state to another. Will not start if another transition is in
+     * progress.
      */
-    fun startTransition(info: TransitionInfo): UUID? {
-        if (currentTransitionInfo != null) {
-            // Open questions:
-            // * Queue of transitions? buffer of 1?
-            // * Are transitions cancellable if a new one is triggered?
-            // * What validation does this need to do?
-            Log.wtf(TAG, "Transition still active: $currentTransitionInfo")
-            return null
-        }
-        currentTransitionInfo?.animator?.cancel()
-
-        currentTransitionInfo = info
-        info.animator?.let { animator ->
-            // An animator was provided, so use it to run the transition
-            animator.setFloatValues(0f, 1f)
-            val updateListener =
-                object : AnimatorUpdateListener {
-                    override fun onAnimationUpdate(animation: ValueAnimator) {
-                        emitTransition(
-                            info,
-                            (animation.getAnimatedValue() as Float),
-                            TransitionState.RUNNING
-                        )
-                    }
-                }
-            val adapter =
-                object : AnimatorListenerAdapter() {
-                    override fun onAnimationStart(animation: Animator) {
-                        Log.i(TAG, "Starting transition: $info")
-                        emitTransition(info, 0f, TransitionState.STARTED)
-                    }
-                    override fun onAnimationCancel(animation: Animator) {
-                        Log.i(TAG, "Cancelling transition: $info")
-                    }
-                    override fun onAnimationEnd(animation: Animator) {
-                        Log.i(TAG, "Ending transition: $info")
-                        emitTransition(info, 1f, TransitionState.FINISHED)
-                        animator.removeListener(this)
-                        animator.removeUpdateListener(updateListener)
-                    }
-                }
-            animator.addListener(adapter)
-            animator.addUpdateListener(updateListener)
-            animator.start()
-            return@startTransition null
-        }
-            ?: run {
-                Log.i(TAG, "Starting transition (manual): $info")
-                emitTransition(info, 0f, TransitionState.STARTED)
-
-                // No animator, so it's manual. Provide a mechanism to callback
-                updateTransitionId = UUID.randomUUID()
-                return@startTransition updateTransitionId
-            }
-    }
+    fun startTransition(info: TransitionInfo): UUID?
 
     /**
      * Allows manual control of a transition. When calling [startTransition], the consumer must pass
@@ -132,58 +83,127 @@
         transitionId: UUID,
         @FloatRange(from = 0.0, to = 1.0) value: Float,
         state: TransitionState
+    )
+}
+
+@SysUISingleton
+class KeyguardTransitionRepositoryImpl @Inject constructor() : KeyguardTransitionRepository {
+    /*
+     * Each transition between [KeyguardState]s will have an associated Flow.
+     * In order to collect these events, clients should call [transition].
+     */
+    private val _transitions =
+        MutableSharedFlow<TransitionStep>(
+            extraBufferCapacity = 10,
+            onBufferOverflow = BufferOverflow.DROP_OLDEST
+        )
+    override val transitions = _transitions.asSharedFlow().distinctUntilChanged()
+    private var lastStep: TransitionStep = TransitionStep()
+
+    /*
+     * When manual control of the transition is requested, a unique [UUID] is used as the handle
+     * to permit calls to [updateTransition]
+     */
+    private var updateTransitionId: UUID? = null
+
+    override fun startTransition(info: TransitionInfo): UUID? {
+        if (lastStep.transitionState != TransitionState.FINISHED) {
+            // Open questions:
+            // * Queue of transitions? buffer of 1?
+            // * Are transitions cancellable if a new one is triggered?
+            // * What validation does this need to do?
+            Log.wtf(TAG, "Transition still active: $lastStep")
+            return null
+        }
+
+        info.animator?.let { animator ->
+            // An animator was provided, so use it to run the transition
+            animator.setFloatValues(0f, 1f)
+            val updateListener =
+                object : AnimatorUpdateListener {
+                    override fun onAnimationUpdate(animation: ValueAnimator) {
+                        emitTransition(
+                            TransitionStep(
+                                info,
+                                (animation.getAnimatedValue() as Float),
+                                TransitionState.RUNNING
+                            )
+                        )
+                    }
+                }
+            val adapter =
+                object : AnimatorListenerAdapter() {
+                    override fun onAnimationStart(animation: Animator) {
+                        emitTransition(TransitionStep(info, 0f, TransitionState.STARTED))
+                    }
+                    override fun onAnimationCancel(animation: Animator) {
+                        Log.i(TAG, "Cancelling transition: $info")
+                    }
+                    override fun onAnimationEnd(animation: Animator) {
+                        emitTransition(TransitionStep(info, 1f, TransitionState.FINISHED))
+                        animator.removeListener(this)
+                        animator.removeUpdateListener(updateListener)
+                    }
+                }
+            animator.addListener(adapter)
+            animator.addUpdateListener(updateListener)
+            animator.start()
+            return@startTransition null
+        }
+            ?: run {
+                emitTransition(TransitionStep(info, 0f, TransitionState.STARTED))
+
+                // No animator, so it's manual. Provide a mechanism to callback
+                updateTransitionId = UUID.randomUUID()
+                return@startTransition updateTransitionId
+            }
+    }
+
+    override fun updateTransition(
+        transitionId: UUID,
+        @FloatRange(from = 0.0, to = 1.0) value: Float,
+        state: TransitionState
     ) {
         if (updateTransitionId != transitionId) {
             Log.wtf(TAG, "Attempting to update with old/invalid transitionId: $transitionId")
             return
         }
 
-        if (currentTransitionInfo == null) {
-            Log.wtf(TAG, "Attempting to update with null 'currentTransitionInfo'")
-            return
+        if (state == TransitionState.FINISHED) {
+            updateTransitionId = null
         }
 
-        currentTransitionInfo?.let { info ->
-            if (state == TransitionState.FINISHED) {
-                updateTransitionId = null
-                Log.i(TAG, "Ending transition: $info")
-            }
-
-            emitTransition(info, value, state)
-        }
+        val nextStep = lastStep.copy(value = value, transitionState = state)
+        emitTransition(nextStep, isManual = true)
     }
 
-    private fun emitTransition(
-        info: TransitionInfo,
-        value: Float,
-        transitionState: TransitionState
-    ) {
-        trace(info, transitionState)
-
-        if (transitionState == TransitionState.FINISHED) {
-            currentTransitionInfo = null
+    private fun emitTransition(nextStep: TransitionStep, isManual: Boolean = false) {
+        trace(nextStep, isManual)
+        val emitted = _transitions.tryEmit(nextStep)
+        if (!emitted) {
+            Log.w(TAG, "Failed to emit next value without suspending")
         }
-        _transitions.value = TransitionStep(info.from, info.to, value, transitionState)
+        lastStep = nextStep
     }
 
-    private fun trace(info: TransitionInfo, transitionState: TransitionState) {
+    private fun trace(step: TransitionStep, isManual: Boolean) {
         if (
-            transitionState != TransitionState.STARTED &&
-                transitionState != TransitionState.FINISHED
+            step.transitionState != TransitionState.STARTED &&
+                step.transitionState != TransitionState.FINISHED
         ) {
             return
         }
         val traceName =
-            "Transition: ${info.from} -> ${info.to} " +
-                if (info.animator == null) {
+            "Transition: ${step.from} -> ${step.to} " +
+                if (isManual) {
                     "(manual)"
                 } else {
                     ""
                 }
         val traceCookie = traceName.hashCode()
-        if (transitionState == TransitionState.STARTED) {
+        if (step.transitionState == TransitionState.STARTED) {
             Trace.beginAsyncSection(traceName, traceCookie)
-        } else if (transitionState == TransitionState.FINISHED) {
+        } else if (step.transitionState == TransitionState.FINISHED) {
             Trace.endAsyncSection(traceName, traceCookie)
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt
index 4003766..0aeff7f 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodLockscreenTransitionInteractor.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.collect
@@ -36,31 +37,35 @@
     @Application private val scope: CoroutineScope,
     private val keyguardRepository: KeyguardRepository,
     private val keyguardTransitionRepository: KeyguardTransitionRepository,
+    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
 ) : TransitionInteractor("AOD<->LOCKSCREEN") {
 
     override fun start() {
         scope.launch {
-            keyguardRepository.isDozing.collect { isDozing ->
-                if (isDozing) {
-                    keyguardTransitionRepository.startTransition(
-                        TransitionInfo(
-                            name,
-                            KeyguardState.LOCKSCREEN,
-                            KeyguardState.AOD,
-                            getAnimator(),
+            keyguardRepository.isDozing
+                .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) })
+                .collect { pair ->
+                    val (isDozing, keyguardState) = pair
+                    if (isDozing && keyguardState == KeyguardState.LOCKSCREEN) {
+                        keyguardTransitionRepository.startTransition(
+                            TransitionInfo(
+                                name,
+                                KeyguardState.LOCKSCREEN,
+                                KeyguardState.AOD,
+                                getAnimator(),
+                            )
                         )
-                    )
-                } else {
-                    keyguardTransitionRepository.startTransition(
-                        TransitionInfo(
-                            name,
-                            KeyguardState.AOD,
-                            KeyguardState.LOCKSCREEN,
-                            getAnimator(),
+                    } else if (!isDozing && keyguardState == KeyguardState.AOD) {
+                        keyguardTransitionRepository.startTransition(
+                            TransitionInfo(
+                                name,
+                                KeyguardState.AOD,
+                                KeyguardState.LOCKSCREEN,
+                                getAnimator(),
+                            )
                         )
-                    )
+                    }
                 }
-            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt
new file mode 100644
index 0000000..7e01db3
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/AodToGoneTransitionInteractor.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2022 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.keyguard.domain.interactor
+
+import android.animation.ValueAnimator
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.WAKE_AND_UNLOCK
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.util.kotlin.sample
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class AodToGoneTransitionInteractor
+@Inject
+constructor(
+    @Application private val scope: CoroutineScope,
+    private val keyguardInteractor: KeyguardInteractor,
+    private val keyguardTransitionRepository: KeyguardTransitionRepository,
+    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+) : TransitionInteractor("AOD->GONE") {
+
+    private val wakeAndUnlockModes =
+        setOf(WAKE_AND_UNLOCK, WAKE_AND_UNLOCK_FROM_DREAM, WAKE_AND_UNLOCK_PULSING)
+
+    override fun start() {
+        scope.launch {
+            keyguardInteractor.biometricUnlockState
+                .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) })
+                .collect { pair ->
+                    val (biometricUnlockState, keyguardState) = pair
+                    if (
+                        keyguardState == KeyguardState.AOD &&
+                            wakeAndUnlockModes.contains(biometricUnlockState)
+                    ) {
+                        keyguardTransitionRepository.startTransition(
+                            TransitionInfo(
+                                name,
+                                KeyguardState.AOD,
+                                KeyguardState.GONE,
+                                getAnimator(),
+                            )
+                        )
+                    }
+                }
+        }
+    }
+
+    private fun getAnimator(): ValueAnimator {
+        return ValueAnimator().apply {
+            setInterpolator(Interpolators.LINEAR)
+            setDuration(TRANSITION_DURATION_MS)
+        }
+    }
+
+    companion object {
+        private const val TRANSITION_DURATION_MS = 500L
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt
index 2af9318..dbb0352 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractor.kt
@@ -30,7 +30,6 @@
 import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.data.BouncerView
 import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
-import com.android.systemui.keyguard.shared.model.BouncerCallbackActionsModel
 import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
 import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
 import com.android.systemui.plugins.ActivityStarter
@@ -40,6 +39,7 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.map
@@ -77,7 +77,7 @@
             KeyguardBouncerModel(
                 promptReason = repository.bouncerPromptReason ?: 0,
                 errorMessage = repository.bouncerErrorMessage,
-                expansionAmount = repository.expansionAmount.value
+                expansionAmount = repository.panelExpansionAmount.value
             )
         )
         repository.setShowingSoon(false)
@@ -90,14 +90,22 @@
     val startingToHide: Flow<Unit> = repository.startingToHide.filter { it }.map {}
     val isVisible: Flow<Boolean> = repository.isVisible
     val isBackButtonEnabled: Flow<Boolean> = repository.isBackButtonEnabled.filterNotNull()
-    val expansionAmount: Flow<Float> = repository.expansionAmount
     val showMessage: Flow<BouncerShowMessageModel> = repository.showMessage.filterNotNull()
     val startingDisappearAnimation: Flow<Runnable> =
         repository.startingDisappearAnimation.filterNotNull()
-    val onDismissAction: Flow<BouncerCallbackActionsModel> =
-        repository.onDismissAction.filterNotNull()
     val resourceUpdateRequests: Flow<Boolean> = repository.resourceUpdateRequests.filter { it }
     val keyguardPosition: Flow<Float> = repository.keyguardPosition
+    val panelExpansionAmount: Flow<Float> = repository.panelExpansionAmount
+    /** 0f = bouncer fully hidden. 1f = bouncer fully visible. */
+    val bouncerExpansion: Flow<Float> = //
+        combine(repository.panelExpansionAmount, repository.isVisible) { expansionAmount, isVisible
+            ->
+            if (isVisible) {
+                1f - expansionAmount
+            } else {
+                0f
+            }
+        }
 
     // TODO(b/243685699): Move isScrimmed logic to data layer.
     // TODO(b/243695312): Encapsulate all of the show logic for the bouncer.
@@ -128,7 +136,7 @@
         Trace.beginSection("KeyguardBouncer#show")
         repository.setScrimmed(isScrimmed)
         if (isScrimmed) {
-            setExpansion(KeyguardBouncer.EXPANSION_VISIBLE)
+            setPanelExpansion(KeyguardBouncer.EXPANSION_VISIBLE)
         }
 
         if (resumeBouncer) {
@@ -149,7 +157,6 @@
         }
         keyguardStateController.notifyBouncerShowing(true)
         callbackInteractor.dispatchStartingToShow()
-
         Trace.endSection()
     }
 
@@ -168,7 +175,6 @@
         keyguardStateController.notifyBouncerShowing(false /* showing */)
         cancelShowRunnable()
         repository.setShowingSoon(false)
-        repository.setOnDismissAction(null)
         repository.setVisible(false)
         repository.setHide(true)
         repository.setShow(null)
@@ -176,14 +182,17 @@
     }
 
     /**
-     * Sets the panel expansion which is calculated further upstream. Expansion is from 0f to 1f
-     * where 0f => showing and 1f => hiding
+     * Sets the panel expansion which is calculated further upstream. Panel expansion is from 0f
+     * (panel fully hidden) to 1f (panel fully showing). As the panel shows (from 0f => 1f), the
+     * bouncer hides and as the panel becomes hidden (1f => 0f), the bouncer starts to show.
+     * Therefore, a panel expansion of 1f represents the bouncer fully hidden and a panel expansion
+     * of 0f represents the bouncer fully showing.
      */
-    fun setExpansion(expansion: Float) {
-        val oldExpansion = repository.expansionAmount.value
+    fun setPanelExpansion(expansion: Float) {
+        val oldExpansion = repository.panelExpansionAmount.value
         val expansionChanged = oldExpansion != expansion
         if (repository.startingDisappearAnimation.value == null) {
-            repository.setExpansion(expansion)
+            repository.setPanelExpansion(expansion)
         }
 
         if (
@@ -227,7 +236,7 @@
         onDismissAction: ActivityStarter.OnDismissAction?,
         cancelAction: Runnable?
     ) {
-        repository.setOnDismissAction(BouncerCallbackActionsModel(onDismissAction, cancelAction))
+        bouncerView.delegate?.setDismissAction(onDismissAction, cancelAction)
     }
 
     /** Update the resources of the views. */
@@ -282,7 +291,7 @@
     /** Returns whether bouncer is fully showing. */
     fun isFullyShowing(): Boolean {
         return (repository.showingSoon.value || repository.isVisible.value) &&
-            repository.expansionAmount.value == KeyguardBouncer.EXPANSION_VISIBLE &&
+            repository.panelExpansionAmount.value == KeyguardBouncer.EXPANSION_VISIBLE &&
             repository.startingDisappearAnimation.value == null
     }
 
@@ -294,8 +303,8 @@
     /** If bouncer expansion is between 0f and 1f non-inclusive. */
     fun isInTransit(): Boolean {
         return repository.showingSoon.value ||
-            repository.expansionAmount.value != KeyguardBouncer.EXPANSION_HIDDEN &&
-                repository.expansionAmount.value != KeyguardBouncer.EXPANSION_VISIBLE
+            repository.panelExpansionAmount.value != KeyguardBouncer.EXPANSION_HIDDEN &&
+                repository.panelExpansionAmount.value != KeyguardBouncer.EXPANSION_VISIBLE
     }
 
     /** Return whether bouncer is animating away. */
@@ -305,7 +314,7 @@
 
     /** Return whether bouncer will dismiss with actions */
     fun willDismissWithAction(): Boolean {
-        return repository.onDismissAction.value?.onDismissAction != null
+        return bouncerView.delegate?.willDismissWithActions() == true
     }
 
     /** Returns whether the bouncer should be full screen. */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GoneAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GoneAodTransitionInteractor.kt
new file mode 100644
index 0000000..0e2a54c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/GoneAodTransitionInteractor.kt
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2022 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.keyguard.domain.interactor
+
+import android.animation.ValueAnimator
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.WakefulnessModel
+import com.android.systemui.util.kotlin.sample
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class GoneAodTransitionInteractor
+@Inject
+constructor(
+    @Application private val scope: CoroutineScope,
+    private val keyguardInteractor: KeyguardInteractor,
+    private val keyguardTransitionRepository: KeyguardTransitionRepository,
+    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+) : TransitionInteractor("GONE->AOD") {
+
+    override fun start() {
+        scope.launch {
+            keyguardInteractor.wakefulnessState
+                .sample(keyguardTransitionInteractor.finishedKeyguardState, { a, b -> Pair(a, b) })
+                .collect { pair ->
+                    val (wakefulnessState, keyguardState) = pair
+                    if (
+                        keyguardState == KeyguardState.GONE &&
+                            wakefulnessState == WakefulnessModel.STARTING_TO_SLEEP
+                    ) {
+                        keyguardTransitionRepository.startTransition(
+                            TransitionInfo(
+                                name,
+                                KeyguardState.GONE,
+                                KeyguardState.AOD,
+                                getAnimator(),
+                            )
+                        )
+                    }
+                }
+        }
+    }
+
+    private fun getAnimator(): ValueAnimator {
+        return ValueAnimator().apply {
+            setInterpolator(Interpolators.LINEAR)
+            setDuration(TRANSITION_DURATION_MS)
+        }
+    }
+
+    companion object {
+        private const val TRANSITION_DURATION_MS = 500L
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index fc2269c..614ff8d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -19,6 +19,9 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.keyguard.shared.model.WakefulnessModel
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 
@@ -38,8 +41,19 @@
     val dozeAmount: Flow<Float> = repository.dozeAmount
     /** Whether the system is in doze mode. */
     val isDozing: Flow<Boolean> = repository.isDozing
-    /** Whether the keyguard is showing to not. */
+    /** Whether the keyguard is showing or not. */
     val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing
+    /** Whether the bouncer is showing or not. */
+    val isBouncerShowing: Flow<Boolean> = repository.isBouncerShowing
+    /** The device wake/sleep state */
+    val wakefulnessState: Flow<WakefulnessModel> = repository.wakefulnessState
+    /** Observable for the [StatusBarState] */
+    val statusBarState: Flow<StatusBarState> = repository.statusBarState
+    /**
+     * Observable for [BiometricUnlockModel] when biometrics like face or any fingerprint (rear,
+     * side, under display) is used to unlock the device.
+     */
+    val biometricUnlockState: Flow<BiometricUnlockModel> = repository.biometricUnlockState
 
     fun isKeyguardShowing(): Boolean {
         return repository.isKeyguardShowing()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index 914b9fc..92caa89 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -18,19 +18,30 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.content.Intent
+import android.util.Log
 import com.android.internal.widget.LockPatternUtils
 import com.android.systemui.animation.Expandable
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
 import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
 import com.android.systemui.keyguard.domain.quickaffordance.KeyguardQuickAffordanceRegistry
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation
+import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation
 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.settings.UserTracker
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import dagger.Lazy
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onStart
 
 @SysUISingleton
@@ -43,7 +54,12 @@
     private val keyguardStateController: KeyguardStateController,
     private val userTracker: UserTracker,
     private val activityStarter: ActivityStarter,
+    private val featureFlags: FeatureFlags,
+    private val repository: Lazy<KeyguardQuickAffordanceRepository>,
 ) {
+    private val isUsingRepository: Boolean
+        get() = featureFlags.isEnabled(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES)
+
     /** Returns an observable for the quick affordance at the given position. */
     fun quickAffordance(
         position: KeyguardQuickAffordancePosition
@@ -62,48 +78,174 @@
     }
 
     /**
-     * Notifies that a quick affordance has been clicked by the user.
+     * Notifies that a quick affordance has been "triggered" (clicked) by the user.
      *
      * @param configKey The configuration key corresponding to the [KeyguardQuickAffordanceModel] of
      * the affordance that was clicked
      * @param expandable An optional [Expandable] for the activity- or dialog-launch animation
      */
-    fun onQuickAffordanceClicked(
+    fun onQuickAffordanceTriggered(
         configKey: String,
         expandable: Expandable?,
     ) {
-        @Suppress("UNCHECKED_CAST") val config = registry.get(configKey)
-        when (val result = config.onQuickAffordanceClicked(expandable)) {
-            is KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity ->
+        @Suppress("UNCHECKED_CAST")
+        val config =
+            if (isUsingRepository) {
+                val (slotId, decodedConfigKey) = configKey.decode()
+                repository.get().selections.value[slotId]?.find { it.key == decodedConfigKey }
+            } else {
+                registry.get(configKey)
+            }
+        if (config == null) {
+            Log.e(TAG, "Affordance config with key of \"$configKey\" not found!")
+            return
+        }
+
+        when (val result = config.onTriggered(expandable)) {
+            is KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity ->
                 launchQuickAffordance(
                     intent = result.intent,
                     canShowWhileLocked = result.canShowWhileLocked,
                     expandable = expandable,
                 )
-            is KeyguardQuickAffordanceConfig.OnClickedResult.Handled -> Unit
+            is KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled -> Unit
+        }
+    }
+
+    /**
+     * Selects an affordance with the given ID on the slot with the given ID.
+     *
+     * @return `true` if the affordance was selected successfully; `false` otherwise.
+     */
+    suspend fun select(slotId: String, affordanceId: String): Boolean {
+        check(isUsingRepository)
+
+        val slots = repository.get().getSlotPickerRepresentations()
+        val slot = slots.find { it.id == slotId } ?: return false
+        val selections =
+            repository.get().getSelections().getOrDefault(slotId, emptyList()).toMutableList()
+        val alreadySelected = selections.remove(affordanceId)
+        if (!alreadySelected) {
+            while (selections.size > 0 && selections.size >= slot.maxSelectedAffordances) {
+                selections.removeAt(0)
+            }
+        }
+
+        selections.add(affordanceId)
+
+        repository
+            .get()
+            .setSelections(
+                slotId = slotId,
+                affordanceIds = selections,
+            )
+
+        return true
+    }
+
+    /**
+     * Unselects one or all affordances from the slot with the given ID.
+     *
+     * @param slotId The ID of the slot.
+     * @param affordanceId The ID of the affordance to remove; if `null`, removes all affordances
+     * from the slot.
+     * @return `true` if the affordance was successfully removed; `false` otherwise (for example, if
+     * the affordance was not on the slot to begin with).
+     */
+    suspend fun unselect(slotId: String, affordanceId: String?): Boolean {
+        check(isUsingRepository)
+
+        val slots = repository.get().getSlotPickerRepresentations()
+        if (slots.find { it.id == slotId } == null) {
+            return false
+        }
+
+        if (affordanceId.isNullOrEmpty()) {
+            return if (
+                repository.get().getSelections().getOrDefault(slotId, emptyList()).isEmpty()
+            ) {
+                false
+            } else {
+                repository.get().setSelections(slotId = slotId, affordanceIds = emptyList())
+                true
+            }
+        }
+
+        val selections =
+            repository.get().getSelections().getOrDefault(slotId, emptyList()).toMutableList()
+        return if (selections.remove(affordanceId)) {
+            repository
+                .get()
+                .setSelections(
+                    slotId = slotId,
+                    affordanceIds = selections,
+                )
+            true
+        } else {
+            false
+        }
+    }
+
+    /** Returns affordance IDs indexed by slot ID, for all known slots. */
+    suspend fun getSelections(): Map<String, List<String>> {
+        check(isUsingRepository)
+
+        val selections = repository.get().getSelections()
+        return repository.get().getSlotPickerRepresentations().associate { slotRepresentation ->
+            slotRepresentation.id to (selections[slotRepresentation.id] ?: emptyList())
         }
     }
 
     private fun quickAffordanceInternal(
         position: KeyguardQuickAffordancePosition
     ): Flow<KeyguardQuickAffordanceModel> {
-        val configs = registry.getAll(position)
+        return if (isUsingRepository) {
+            repository
+                .get()
+                .selections
+                .map { it[position.toSlotId()] ?: emptyList() }
+                .flatMapLatest { configs -> combinedConfigs(position, configs) }
+        } else {
+            combinedConfigs(position, registry.getAll(position))
+        }
+    }
+
+    private fun combinedConfigs(
+        position: KeyguardQuickAffordancePosition,
+        configs: List<KeyguardQuickAffordanceConfig>,
+    ): Flow<KeyguardQuickAffordanceModel> {
+        if (configs.isEmpty()) {
+            return flowOf(KeyguardQuickAffordanceModel.Hidden)
+        }
+
         return combine(
             configs.map { config ->
-                // We emit an initial "Hidden" value to make sure that there's always an initial
-                // value and avoid subtle bugs where the downstream isn't receiving any values
-                // because one config implementation is not emitting an initial value. For example,
-                // see b/244296596.
-                config.state.onStart { emit(KeyguardQuickAffordanceConfig.State.Hidden) }
+                // We emit an initial "Hidden" value to make sure that there's always an
+                // initial value and avoid subtle bugs where the downstream isn't receiving
+                // any values because one config implementation is not emitting an initial
+                // value. For example, see b/244296596.
+                config.lockScreenState.onStart {
+                    emit(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
+                }
             }
         ) { states ->
-            val index = states.indexOfFirst { it is KeyguardQuickAffordanceConfig.State.Visible }
+            val index =
+                states.indexOfFirst { state ->
+                    state is KeyguardQuickAffordanceConfig.LockScreenState.Visible
+                }
             if (index != -1) {
-                val visibleState = states[index] as KeyguardQuickAffordanceConfig.State.Visible
+                val visibleState =
+                    states[index] as KeyguardQuickAffordanceConfig.LockScreenState.Visible
+                val configKey = configs[index].key
                 KeyguardQuickAffordanceModel.Visible(
-                    configKey = configs[index].key,
+                    configKey =
+                        if (isUsingRepository) {
+                            configKey.encode(position.toSlotId())
+                        } else {
+                            configKey
+                        },
                     icon = visibleState.icon,
-                    toggle = visibleState.toggle,
+                    activationState = visibleState.activationState,
                 )
             } else {
                 KeyguardQuickAffordanceModel.Hidden
@@ -141,4 +283,39 @@
             )
         }
     }
+
+    private fun KeyguardQuickAffordancePosition.toSlotId(): String {
+        return when (this) {
+            KeyguardQuickAffordancePosition.BOTTOM_START ->
+                KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
+            KeyguardQuickAffordancePosition.BOTTOM_END ->
+                KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END
+        }
+    }
+
+    private fun String.encode(slotId: String): String {
+        return "$slotId$DELIMITER$this"
+    }
+
+    private fun String.decode(): Pair<String, String> {
+        val splitUp = this.split(DELIMITER)
+        return Pair(splitUp[0], splitUp[1])
+    }
+
+    fun getAffordancePickerRepresentations(): List<KeyguardQuickAffordancePickerRepresentation> {
+        check(isUsingRepository)
+
+        return repository.get().getAffordancePickerRepresentations()
+    }
+
+    fun getSlotPickerRepresentations(): List<KeyguardSlotPickerRepresentation> {
+        check(isUsingRepository)
+
+        return repository.get().getSlotPickerRepresentations()
+    }
+
+    companion object {
+        private const val TAG = "KeyguardQuickAffordanceInteractor"
+        private const val DELIMITER = "::"
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
new file mode 100644
index 0000000..57fb4a1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionAuditLogger.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2022 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.keyguard.domain.interactor
+
+import com.android.keyguard.logging.KeyguardLogger
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+/** Collect flows of interest for auditing keyguard transitions. */
+@SysUISingleton
+class KeyguardTransitionAuditLogger
+@Inject
+constructor(
+    @Application private val scope: CoroutineScope,
+    private val interactor: KeyguardTransitionInteractor,
+    private val keyguardInteractor: KeyguardInteractor,
+    private val logger: KeyguardLogger,
+) {
+
+    fun start() {
+        scope.launch {
+            keyguardInteractor.wakefulnessState.collect { logger.v("WakefulnessState", it) }
+        }
+
+        scope.launch {
+            interactor.finishedKeyguardTransitionStep.collect {
+                logger.i("Finished transition", it)
+            }
+        }
+
+        scope.launch {
+            interactor.startedKeyguardTransitionStep.collect { logger.i("Started transition", it) }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
index b166681..a7c6d44 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
@@ -26,6 +26,7 @@
 @Inject
 constructor(
     private val interactors: Set<TransitionInteractor>,
+    private val auditLogger: KeyguardTransitionAuditLogger,
 ) : CoreStartable {
 
     override fun start() {
@@ -38,9 +39,13 @@
                 when (it) {
                     is LockscreenBouncerTransitionInteractor -> Log.d(TAG, "Started $it")
                     is AodLockscreenTransitionInteractor -> Log.d(TAG, "Started $it")
+                    is GoneAodTransitionInteractor -> Log.d(TAG, "Started $it")
+                    is LockscreenGoneTransitionInteractor -> Log.d(TAG, "Started $it")
+                    is AodToGoneTransitionInteractor -> Log.d(TAG, "Started $it")
                 }
             it.start()
         }
+        auditLogger.start()
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
index 7409aec..749183e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractor.kt
@@ -19,11 +19,14 @@
 
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.merge
 
@@ -40,6 +43,10 @@
     /** LOCKSCREEN->AOD transition information. */
     val lockscreenToAodTransition: Flow<TransitionStep> = repository.transition(LOCKSCREEN, AOD)
 
+    /** (any)->AOD transition information */
+    val anyStateToAodTransition: Flow<TransitionStep> =
+        repository.transitions.filter { step -> step.to == KeyguardState.AOD }
+
     /**
      * AOD<->LOCKSCREEN transition information, mapped to dozeAmount range of AOD (1f) <->
      * Lockscreen (0f).
@@ -49,4 +56,16 @@
             aodToLockscreenTransition.map { step -> step.copy(value = 1f - step.value) },
             lockscreenToAodTransition,
         )
+
+    /* The last [TransitionStep] with a [TransitionState] of FINISHED */
+    val finishedKeyguardTransitionStep: Flow<TransitionStep> =
+        repository.transitions.filter { step -> step.transitionState == TransitionState.FINISHED }
+
+    /* The last completed [KeyguardState] transition */
+    val finishedKeyguardState: Flow<KeyguardState> =
+        finishedKeyguardTransitionStep.map { step -> step.to }
+
+    /* The last [TransitionStep] with a [TransitionState] of STARTED */
+    val startedKeyguardTransitionStep: Flow<TransitionStep> =
+        repository.transitions.filter { step -> step.transitionState == TransitionState.STARTED }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
index 3c2a12e..fd4814d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenBouncerTransitionInteractor.kt
@@ -16,19 +16,22 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+import android.animation.ValueAnimator
+import com.android.systemui.animation.Interpolators
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.keyguard.data.repository.KeyguardRepository
 import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.StatusBarState.SHADE_LOCKED
 import com.android.systemui.keyguard.shared.model.TransitionInfo
 import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.WakefulnessModel
 import com.android.systemui.shade.data.repository.ShadeRepository
 import com.android.systemui.util.kotlin.sample
 import java.util.UUID
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.launch
 
@@ -37,62 +40,112 @@
 @Inject
 constructor(
     @Application private val scope: CoroutineScope,
-    private val keyguardRepository: KeyguardRepository,
+    private val keyguardInteractor: KeyguardInteractor,
     private val shadeRepository: ShadeRepository,
     private val keyguardTransitionRepository: KeyguardTransitionRepository,
+    private val keyguardTransitionInteractor: KeyguardTransitionInteractor
 ) : TransitionInteractor("LOCKSCREEN<->BOUNCER") {
 
     private var transitionId: UUID? = null
 
     override fun start() {
-        scope.launch {
-            shadeRepository.shadeModel.sample(
-                combine(
-                    keyguardTransitionRepository.transitions,
-                    keyguardRepository.statusBarState,
-                ) { transitions, statusBarState ->
-                    Pair(transitions, statusBarState)
-                }
-            ) { shadeModel, pair ->
-                val (transitions, statusBarState) = pair
+        listenForDraggingUpToBouncer()
+        listenForBouncerHiding()
+    }
 
-                val id = transitionId
-                if (id != null) {
-                    // An existing `id` means a transition is started, and calls to
-                    // `updateTransition` will control it until FINISHED
-                    keyguardTransitionRepository.updateTransition(
-                        id,
-                        shadeModel.expansionAmount,
-                        if (shadeModel.expansionAmount == 0f || shadeModel.expansionAmount == 1f) {
-                            transitionId = null
-                            TransitionState.FINISHED
-                        } else {
-                            TransitionState.RUNNING
-                        }
-                    )
-                } else {
-                    // TODO (b/251849525): Remove statusbarstate check when that state is integrated
-                    // into KeyguardTransitionRepository
-                    val isOnLockscreen =
-                        transitions.transitionState == TransitionState.FINISHED &&
-                            transitions.to == KeyguardState.LOCKSCREEN
-                    if (
-                        isOnLockscreen &&
-                            shadeModel.isUserDragging &&
-                            statusBarState != SHADE_LOCKED
-                    ) {
-                        transitionId =
-                            keyguardTransitionRepository.startTransition(
-                                TransitionInfo(
-                                    ownerName = name,
-                                    from = KeyguardState.LOCKSCREEN,
-                                    to = KeyguardState.BOUNCER,
-                                    animator = null,
-                                )
+    private fun listenForBouncerHiding() {
+        scope.launch {
+            keyguardInteractor.isBouncerShowing
+                .sample(keyguardInteractor.wakefulnessState, { a, b -> Pair(a, b) })
+                .collect { pair ->
+                    val (isBouncerShowing, wakefulnessState) = pair
+                    if (!isBouncerShowing) {
+                        val to =
+                            if (
+                                wakefulnessState == WakefulnessModel.STARTING_TO_SLEEP ||
+                                    wakefulnessState == WakefulnessModel.ASLEEP
+                            ) {
+                                KeyguardState.AOD
+                            } else {
+                                KeyguardState.LOCKSCREEN
+                            }
+                        keyguardTransitionRepository.startTransition(
+                            TransitionInfo(
+                                ownerName = name,
+                                from = KeyguardState.BOUNCER,
+                                to = to,
+                                animator = getAnimator(),
                             )
+                        )
                     }
                 }
-            }
         }
     }
+
+    /* Starts transitions when manually dragging up the bouncer from the lockscreen. */
+    private fun listenForDraggingUpToBouncer() {
+        scope.launch {
+            shadeRepository.shadeModel
+                .sample(
+                    combine(
+                        keyguardTransitionInteractor.finishedKeyguardState,
+                        keyguardInteractor.statusBarState,
+                    ) { keyguardState, statusBarState ->
+                        Pair(keyguardState, statusBarState)
+                    },
+                    { shadeModel, pair -> Triple(shadeModel, pair.first, pair.second) }
+                )
+                .collect { triple ->
+                    val (shadeModel, keyguardState, statusBarState) = triple
+
+                    val id = transitionId
+                    if (id != null) {
+                        // An existing `id` means a transition is started, and calls to
+                        // `updateTransition` will control it until FINISHED
+                        keyguardTransitionRepository.updateTransition(
+                            id,
+                            shadeModel.expansionAmount,
+                            if (
+                                shadeModel.expansionAmount == 0f || shadeModel.expansionAmount == 1f
+                            ) {
+                                transitionId = null
+                                TransitionState.FINISHED
+                            } else {
+                                TransitionState.RUNNING
+                            }
+                        )
+                    } else {
+                        // TODO (b/251849525): Remove statusbarstate check when that state is
+                        // integrated
+                        // into KeyguardTransitionRepository
+                        if (
+                            keyguardState == KeyguardState.LOCKSCREEN &&
+                                shadeModel.isUserDragging &&
+                                statusBarState != SHADE_LOCKED
+                        ) {
+                            transitionId =
+                                keyguardTransitionRepository.startTransition(
+                                    TransitionInfo(
+                                        ownerName = name,
+                                        from = KeyguardState.LOCKSCREEN,
+                                        to = KeyguardState.BOUNCER,
+                                        animator = null,
+                                    )
+                                )
+                        }
+                    }
+                }
+        }
+    }
+
+    private fun getAnimator(): ValueAnimator {
+        return ValueAnimator().apply {
+            setInterpolator(Interpolators.LINEAR)
+            setDuration(TRANSITION_DURATION_MS)
+        }
+    }
+
+    companion object {
+        private const val TRANSITION_DURATION_MS = 300L
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenGoneTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenGoneTransitionInteractor.kt
new file mode 100644
index 0000000..6c1adbd
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LockscreenGoneTransitionInteractor.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 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.keyguard.domain.interactor
+
+import android.animation.ValueAnimator
+import com.android.systemui.animation.Interpolators
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+@SysUISingleton
+class LockscreenGoneTransitionInteractor
+@Inject
+constructor(
+    @Application private val scope: CoroutineScope,
+    private val keyguardInteractor: KeyguardInteractor,
+    private val keyguardTransitionRepository: KeyguardTransitionRepository,
+) : TransitionInteractor("LOCKSCREEN->GONE") {
+
+    override fun start() {
+        scope.launch {
+            keyguardInteractor.isKeyguardShowing.collect { isShowing ->
+                if (!isShowing) {
+                    keyguardTransitionRepository.startTransition(
+                        TransitionInfo(
+                            name,
+                            KeyguardState.LOCKSCREEN,
+                            KeyguardState.GONE,
+                            getAnimator(),
+                        )
+                    )
+                }
+            }
+        }
+    }
+
+    private fun getAnimator(): ValueAnimator {
+        return ValueAnimator().apply {
+            setInterpolator(Interpolators.LINEAR)
+            setDuration(TRANSITION_DURATION_MS)
+        }
+    }
+
+    companion object {
+        private const val TRANSITION_DURATION_MS = 10L
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
index 74c542c..37f33af 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
@@ -39,4 +39,12 @@
     @Binds
     @IntoSet
     abstract fun aodLockscreen(impl: AodLockscreenTransitionInteractor): TransitionInteractor
+
+    @Binds @IntoSet abstract fun goneAod(impl: GoneAodTransitionInteractor): TransitionInteractor
+
+    @Binds @IntoSet abstract fun aodGone(impl: AodToGoneTransitionInteractor): TransitionInteractor
+
+    @Binds
+    @IntoSet
+    abstract fun lockscreenGone(impl: LockscreenGoneTransitionInteractor): TransitionInteractor
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt
index fc644a9..32560af 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/model/KeyguardQuickAffordanceModel.kt
@@ -18,7 +18,7 @@
 package com.android.systemui.keyguard.domain.model
 
 import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordanceToggleState
+import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
 
 /**
  * Models a "quick affordance" in the keyguard bottom area (for example, a button on the
@@ -34,7 +34,7 @@
         val configKey: String,
         /** An icon for the affordance. */
         val icon: Icon,
-        /** The toggle state for the affordance. */
-        val toggle: KeyguardQuickAffordanceToggleState,
+        /** The activation state of the affordance. */
+        val activationState: ActivationState,
     ) : KeyguardQuickAffordanceModel()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt
new file mode 100644
index 0000000..db709b4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/BiometricUnlockModel.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2022 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.keyguard.shared.model
+
+/** Model device wakefulness states. */
+enum class BiometricUnlockModel {
+    /** Mode in which we don't need to wake up the device when we authenticate. */
+    NONE,
+    /**
+     * Mode in which we wake up the device, and directly dismiss Keyguard. Active when we acquire a
+     * fingerprint while the screen is off and the device was sleeping.
+     */
+    WAKE_AND_UNLOCK,
+    /**
+     * Mode in which we wake the device up, and fade out the Keyguard contents because they were
+     * already visible while pulsing in doze mode.
+     */
+    WAKE_AND_UNLOCK_PULSING,
+    /**
+     * Mode in which we wake up the device, but play the normal dismiss animation. Active when we
+     * acquire a fingerprint pulsing in doze mode.
+     */
+    SHOW_BOUNCER,
+    /**
+     * Mode in which we only wake up the device, and keyguard was not showing when we authenticated.
+     */
+    ONLY_WAKE,
+    /**
+     * Mode in which fingerprint unlocks the device or passive auth (ie face auth) unlocks the
+     * device while being requested when keyguard is occluded or showing.
+     */
+    UNLOCK_COLLAPSING,
+    /** When bouncer is visible and will be dismissed. */
+    DISMISS_BOUNCER,
+    /** Mode in which fingerprint wakes and unlocks the device from a dream. */
+    WAKE_AND_UNLOCK_FROM_DREAM,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotifPanelEventsModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePickerRepresentation.kt
similarity index 60%
copy from packages/SystemUI/src/com/android/systemui/shade/NotifPanelEventsModule.java
copy to packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePickerRepresentation.kt
index 6772384..a56bc90 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotifPanelEventsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardQuickAffordancePickerRepresentation.kt
@@ -12,19 +12,19 @@
  * 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.shade;
+package com.android.systemui.keyguard.shared.model
 
-import com.android.systemui.dagger.SysUISingleton;
+import androidx.annotation.DrawableRes
 
-import dagger.Binds;
-import dagger.Module;
-
-/** Provides a {@link NotifPanelEvents} in {@link SysUISingleton} scope. */
-@Module
-public abstract class NotifPanelEventsModule {
-    @Binds
-    abstract NotifPanelEvents bindPanelEvents(
-            NotificationPanelViewController.PanelEventsEmitter impl);
-}
+/**
+ * Representation of a quick affordance for use to build "picker", "selector", or "settings"
+ * experiences.
+ */
+data class KeyguardQuickAffordancePickerRepresentation(
+    val id: String,
+    val name: String,
+    @DrawableRes val iconResourceId: Int,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotifPanelEventsModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSlotPickerRepresentation.kt
similarity index 60%
copy from packages/SystemUI/src/com/android/systemui/shade/NotifPanelEventsModule.java
copy to packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSlotPickerRepresentation.kt
index 6772384..86f2756 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotifPanelEventsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardSlotPickerRepresentation.kt
@@ -12,19 +12,17 @@
  * 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.shade;
+package com.android.systemui.keyguard.shared.model
 
-import com.android.systemui.dagger.SysUISingleton;
-
-import dagger.Binds;
-import dagger.Module;
-
-/** Provides a {@link NotifPanelEvents} in {@link SysUISingleton} scope. */
-@Module
-public abstract class NotifPanelEventsModule {
-    @Binds
-    abstract NotifPanelEvents bindPanelEvents(
-            NotificationPanelViewController.PanelEventsEmitter impl);
-}
+/**
+ * Representation of a quick affordance slot (or position) for use to build "picker", "selector", or
+ * "settings" experiences.
+ */
+data class KeyguardSlotPickerRepresentation(
+    val id: String,
+    /** The maximum number of selected affordances that can be present on this slot. */
+    val maxSelectedAffordances: Int = 1,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
index f66d5d3..7958033 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
@@ -17,7 +17,10 @@
 
 /** List of all possible states to transition to/from */
 enum class KeyguardState {
-    /** For initialization only */
+    /**
+     * For initialization as well as when the security method is set to NONE, indicating that
+     * the keyguard should never be shown.
+     */
     NONE,
     /* Always-on Display. The device is in a low-power mode with a minimal UI visible */
     AOD,
@@ -31,4 +34,11 @@
      * unlocked if SWIPE security method is used, or if face lockscreen bypass is false.
      */
     LOCKSCREEN,
+
+    /*
+     * Keyguard is no longer visible. In most cases the user has just authenticated and keyguard
+     * is being removed, but there are other cases where the user is swiping away keyguard, such as
+     * with SWIPE security method or face unlock without bypass.
+     */
+    GONE,
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionState.kt
index d8691c1..0e0465b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionState.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionState.kt
@@ -17,7 +17,6 @@
 
 /** Possible states for a running transition between [State] */
 enum class TransitionState {
-    NONE,
     STARTED,
     RUNNING,
     FINISHED
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt
index 688ec91..732a6f7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TransitionStep.kt
@@ -20,5 +20,12 @@
     val from: KeyguardState = KeyguardState.NONE,
     val to: KeyguardState = KeyguardState.NONE,
     val value: Float = 0f, // constrained [0.0, 1.0]
-    val transitionState: TransitionState = TransitionState.NONE,
-)
+    val transitionState: TransitionState = TransitionState.FINISHED,
+    val ownerName: String = "",
+) {
+    constructor(
+        info: TransitionInfo,
+        value: Float,
+        transitionState: TransitionState,
+    ) : this(info.from, info.to, value, transitionState, info.ownerName)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt
new file mode 100644
index 0000000..64f834d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2022 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.keyguard.shared.model
+
+/** Model device wakefulness states. */
+enum class WakefulnessModel {
+    /** The device is asleep and not interactive. */
+    ASLEEP,
+    /** Received a signal that the device is beginning to wake up. */
+    STARTING_TO_WAKE,
+    /** Device is now fully awake and interactive. */
+    AWAKE,
+    /** Signal that the device is now going to sleep. */
+    STARTING_TO_SLEEP,
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordanceToggleState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/ActivationState.kt
similarity index 68%
rename from packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordanceToggleState.kt
rename to packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/ActivationState.kt
index 55d38a4..a68d190 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordanceToggleState.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/ActivationState.kt
@@ -17,12 +17,12 @@
 
 package com.android.systemui.keyguard.shared.quickaffordance
 
-/** Enumerates all possible toggle states for a quick affordance on the lock-screen. */
-sealed class KeyguardQuickAffordanceToggleState {
-    /** Toggling is not supported. */
-    object NotSupported : KeyguardQuickAffordanceToggleState()
+/** Enumerates all possible activation states for a quick affordance on the lock-screen. */
+sealed class ActivationState {
+    /** Activation is not supported. */
+    object NotSupported : ActivationState()
     /** The quick affordance is on. */
-    object On : KeyguardQuickAffordanceToggleState()
+    object Active : ActivationState()
     /** The quick affordance is off. */
-    object Off : KeyguardQuickAffordanceToggleState()
+    object Inactive : ActivationState()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
index df26014..a22958b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBouncerViewBinder.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.keyguard.data.BouncerViewDelegate
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_VISIBLE
 import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.flow.collect
@@ -75,6 +76,17 @@
                     hostViewController.showPrimarySecurityScreen()
                     hostViewController.onResume()
                 }
+
+                override fun setDismissAction(
+                    onDismissAction: ActivityStarter.OnDismissAction?,
+                    cancelAction: Runnable?
+                ) {
+                    hostViewController.setOnDismissAction(onDismissAction, cancelAction)
+                }
+
+                override fun willDismissWithActions(): Boolean {
+                    return hostViewController.hasDismissActions()
+                }
             }
         view.repeatWhenAttached {
             repeatOnLifecycle(Lifecycle.State.STARTED) {
@@ -122,15 +134,6 @@
                     }
 
                     launch {
-                        viewModel.setDismissAction.collect {
-                            hostViewController.setOnDismissAction(
-                                it.onDismissAction,
-                                it.cancelAction
-                            )
-                        }
-                    }
-
-                    launch {
                         viewModel.startDisappearAnimation.collect {
                             hostViewController.startDisappearAnimation(it)
                         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
index 6aac912..b6b2304 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModel.kt
@@ -22,8 +22,8 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
 import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
+import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
-import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordanceToggleState
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
@@ -118,13 +118,13 @@
                     animateReveal = animateReveal,
                     icon = icon,
                     onClicked = { parameters ->
-                        quickAffordanceInteractor.onQuickAffordanceClicked(
+                        quickAffordanceInteractor.onQuickAffordanceTriggered(
                             configKey = parameters.configKey,
                             expandable = parameters.expandable,
                         )
                     },
                     isClickable = isClickable,
-                    isActivated = toggle is KeyguardQuickAffordanceToggleState.On,
+                    isActivated = activationState is ActivationState.Active,
                 )
             is KeyguardQuickAffordanceModel.Hidden -> KeyguardQuickAffordanceViewModel()
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
index 9ad5211..9a92843 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBouncerViewModel.kt
@@ -20,7 +20,6 @@
 import com.android.systemui.keyguard.data.BouncerView
 import com.android.systemui.keyguard.data.BouncerViewDelegate
 import com.android.systemui.keyguard.domain.interactor.BouncerInteractor
-import com.android.systemui.keyguard.shared.model.BouncerCallbackActionsModel
 import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
 import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
 import com.android.systemui.statusbar.phone.KeyguardBouncer.EXPANSION_VISIBLE
@@ -38,7 +37,7 @@
     private val interactor: BouncerInteractor,
 ) {
     /** Observe on bouncer expansion amount. */
-    val bouncerExpansionAmount: Flow<Float> = interactor.expansionAmount
+    val bouncerExpansionAmount: Flow<Float> = interactor.panelExpansionAmount
 
     /** Observe on bouncer visibility. */
     val isBouncerVisible: Flow<Boolean> = interactor.isVisible
@@ -63,9 +62,6 @@
     /** Observe whether bouncer is starting to hide. */
     val startingToHide: Flow<Unit> = interactor.startingToHide
 
-    /** Observe whether we want to set the dismiss action to the bouncer. */
-    val setDismissAction: Flow<BouncerCallbackActionsModel> = interactor.onDismissAction
-
     /** Observe whether we want to start the disappear animation. */
     val startDisappearAnimation: Flow<Runnable> = interactor.startingDisappearAnimation
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt
index ed649b1..f006442 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaData.kt
@@ -181,6 +181,7 @@
         return enabled == other.enabled &&
             name == other.name &&
             intent == other.intent &&
-            id == other.id
+            id == other.id &&
+            showBroadcastButton == other.showBroadcastButton
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaViewHolder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaViewHolder.kt
index 2511324..a7f1b95 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaViewHolder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/models/player/MediaViewHolder.kt
@@ -26,6 +26,7 @@
 import androidx.constraintlayout.widget.Barrier
 import com.android.systemui.R
 import com.android.systemui.media.controls.models.GutsViewHolder
+import com.android.systemui.ripple.MultiRippleView
 import com.android.systemui.util.animation.TransitionLayout
 
 private const val TAG = "MediaViewHolder"
@@ -36,6 +37,7 @@
 
     // Player information
     val albumView = itemView.requireViewById<ImageView>(R.id.album_art)
+    val multiRippleView = itemView.requireViewById<MultiRippleView>(R.id.touch_ripple_view)
     val appIcon = itemView.requireViewById<ImageView>(R.id.icon)
     val titleText = itemView.requireViewById<TextView>(R.id.header_title)
     val artistText = itemView.requireViewById<TextView>(R.id.header_artist)
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt
index 61ef2f1..918417f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt
@@ -29,6 +29,7 @@
 import com.android.settingslib.Utils
 import com.android.systemui.media.controls.models.player.MediaViewHolder
 import com.android.systemui.monet.ColorScheme
+import com.android.systemui.ripple.MultiRippleController
 
 /**
  * A [ColorTransition] is an object that updates the colors of views each time [updateColorScheme]
@@ -100,12 +101,14 @@
 internal constructor(
     private val context: Context,
     private val mediaViewHolder: MediaViewHolder,
+    private val multiRippleController: MultiRippleController,
     animatingColorTransitionFactory: AnimatingColorTransitionFactory
 ) {
     constructor(
         context: Context,
-        mediaViewHolder: MediaViewHolder
-    ) : this(context, mediaViewHolder, ::AnimatingColorTransition)
+        mediaViewHolder: MediaViewHolder,
+        multiRippleController: MultiRippleController,
+    ) : this(context, mediaViewHolder, multiRippleController, ::AnimatingColorTransition)
 
     val bgColor = context.getColor(com.android.systemui.R.color.material_dynamic_secondary95)
     val surfaceColor =
@@ -125,6 +128,7 @@
             val accentColorList = ColorStateList.valueOf(accentPrimary)
             mediaViewHolder.actionPlayPause.backgroundTintList = accentColorList
             mediaViewHolder.gutsViewHolder.setAccentPrimaryColor(accentPrimary)
+            multiRippleController.updateColor(accentPrimary)
         }
 
     val accentSecondary =
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
index 18ecadb..5b14cf3 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaControlPanel.java
@@ -76,6 +76,8 @@
 import com.android.systemui.broadcast.BroadcastSender;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 import com.android.systemui.media.controls.models.GutsViewHolder;
 import com.android.systemui.media.controls.models.player.MediaAction;
 import com.android.systemui.media.controls.models.player.MediaButton;
@@ -95,6 +97,10 @@
 import com.android.systemui.monet.Style;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.plugins.FalsingManager;
+import com.android.systemui.ripple.MultiRippleController;
+import com.android.systemui.ripple.RippleAnimation;
+import com.android.systemui.ripple.RippleAnimationConfig;
+import com.android.systemui.ripple.RippleShader;
 import com.android.systemui.shared.system.SysUiStatsLog;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
@@ -209,6 +215,8 @@
     private boolean mIsCurrentBroadcastedApp = false;
     private boolean mShowBroadcastDialogButton = false;
     private String mSwitchBroadcastApp;
+    private MultiRippleController mMultiRippleController;
+    private FeatureFlags mFeatureFlags;
 
     /**
      * Initialize a new control panel
@@ -236,7 +244,9 @@
             KeyguardStateController keyguardStateController,
             ActivityIntentHelper activityIntentHelper,
             NotificationLockscreenUserManager lockscreenUserManager,
-            BroadcastDialogController broadcastDialogController) {
+            BroadcastDialogController broadcastDialogController,
+            FeatureFlags featureFlags
+    ) {
         mContext = context;
         mBackgroundExecutor = backgroundExecutor;
         mMainExecutor = mainExecutor;
@@ -262,6 +272,8 @@
             logSmartspaceCardReported(SMARTSPACE_CARD_CLICK_EVENT);
             return Unit.INSTANCE;
         });
+
+        mFeatureFlags = featureFlags;
     }
 
     /**
@@ -381,7 +393,9 @@
         AnimatorSet exit = loadAnimator(R.anim.media_metadata_exit,
                 Interpolators.EMPHASIZED_ACCELERATE, titleText, artistText);
 
-        mColorSchemeTransition = new ColorSchemeTransition(mContext, mMediaViewHolder);
+        mMultiRippleController = new MultiRippleController(vh.getMultiRippleView());
+        mColorSchemeTransition = new ColorSchemeTransition(
+                mContext, mMediaViewHolder, mMultiRippleController);
         mMetadataAnimationHandler = new MetadataAnimationHandler(exit, enter);
     }
 
@@ -982,6 +996,9 @@
                         mLogger.logTapAction(button.getId(), mUid, mPackageName, mInstanceId);
                         logSmartspaceCardReported(SMARTSPACE_CARD_CLICK_EVENT);
                         action.run();
+                        if (mFeatureFlags.isEnabled(Flags.UMO_SURFACE_RIPPLE)) {
+                            mMultiRippleController.play(createTouchRippleAnimation(button));
+                        }
 
                         if (icon instanceof Animatable) {
                             ((Animatable) icon).start();
@@ -997,6 +1014,26 @@
         }
     }
 
+    private RippleAnimation createTouchRippleAnimation(ImageButton button) {
+        float maxSize = mMediaViewHolder.getMultiRippleView().getWidth() * 2;
+        return new RippleAnimation(
+                new RippleAnimationConfig(
+                        RippleShader.RippleShape.CIRCLE,
+                        /* duration= */ 1500L,
+                        /* centerX= */ button.getX() + button.getWidth() * 0.5f,
+                        /* centerY= */ button.getY() + button.getHeight() * 0.5f,
+                        /* maxWidth= */ maxSize,
+                        /* maxHeight= */ maxSize,
+                        /* pixelDensity= */ getContext().getResources().getDisplayMetrics().density,
+                        mColorSchemeTransition.getAccentPrimary().getTargetColor(),
+                        /* opacity= */ 100,
+                        /* shouldFillRipple= */ false,
+                        /* sparkleStrength= */ 0f,
+                        /* shouldDistort= */ false
+                )
+        );
+    }
+
     private void clearButton(final ImageButton button) {
         button.setImageDrawable(null);
         button.setContentDescription(null);
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
index 6b46d8f..cbb670e 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/MediaHierarchyManager.kt
@@ -43,7 +43,8 @@
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.media.dream.MediaDreamComplication
 import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.shade.NotifPanelEvents
+import com.android.systemui.shade.ShadeStateEvents
+import com.android.systemui.shade.ShadeStateEvents.ShadeStateEventsListener
 import com.android.systemui.statusbar.CrossFadeHelper
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.SysuiStatusBarStateController
@@ -96,7 +97,7 @@
     private val dreamOverlayStateController: DreamOverlayStateController,
     configurationController: ConfigurationController,
     wakefulnessLifecycle: WakefulnessLifecycle,
-    panelEventsEvents: NotifPanelEvents,
+    panelEventsEvents: ShadeStateEvents,
     private val secureSettings: SecureSettings,
     @Main private val handler: Handler,
 ) {
@@ -534,8 +535,8 @@
             mediaHosts.forEach { it?.updateViewVisibility() }
         }
 
-        panelEventsEvents.registerListener(
-            object : NotifPanelEvents.Listener {
+        panelEventsEvents.addShadeStateEventsListener(
+            object : ShadeStateEventsListener {
                 override fun onExpandImmediateChanged(isExpandImmediateEnabled: Boolean) {
                     skipQqsOnExpansion = isExpandImmediateEnabled
                     updateDesiredLocation()
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
index 0a60437..769494a 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/common/MediaTttUtils.kt
@@ -27,10 +27,11 @@
 /** Utility methods for media tap-to-transfer. */
 class MediaTttUtils {
     companion object {
-        // Used in CTS tests UpdateMediaTapToTransferSenderDisplayTest and
-        // UpdateMediaTapToTransferReceiverDisplayTest
-        const val WINDOW_TITLE = "Media Transfer Chip View"
-        const val WAKE_REASON = "MEDIA_TRANSFER_ACTIVATED"
+        const val WINDOW_TITLE_SENDER = "Media Transfer Chip View (Sender)"
+        const val WINDOW_TITLE_RECEIVER = "Media Transfer Chip View (Receiver)"
+
+        const val WAKE_REASON_SENDER = "MEDIA_TRANSFER_ACTIVATED_SENDER"
+        const val WAKE_REASON_RECEIVER = "MEDIA_TRANSFER_ACTIVATED_RECEIVER"
 
         /**
          * Returns the information needed to display the icon in [Icon] form.
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
index dc794e6..662d059 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/receiver/MediaTttChipControllerReceiver.kt
@@ -40,7 +40,6 @@
 import com.android.systemui.media.taptotransfer.common.MediaTttUtils
 import com.android.systemui.statusbar.CommandQueue
 import com.android.systemui.statusbar.policy.ConfigurationController
-import com.android.systemui.temporarydisplay.DEFAULT_TIMEOUT_MILLIS
 import com.android.systemui.temporarydisplay.TemporaryViewDisplayController
 import com.android.systemui.temporarydisplay.TemporaryViewInfo
 import com.android.systemui.util.animation.AnimationUtil.Companion.frames
@@ -78,8 +77,6 @@
         configurationController,
         powerManager,
         R.layout.media_ttt_chip_receiver,
-        MediaTttUtils.WINDOW_TITLE,
-        MediaTttUtils.WAKE_REASON,
 ) {
     @SuppressLint("WrongConstant") // We're allowed to use LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
     override val windowLayoutParams = commonWindowLayoutParams.apply {
@@ -192,7 +189,7 @@
     }
 
     private fun startRipple(rippleView: ReceiverChipRippleView) {
-        if (rippleView.rippleInProgress) {
+        if (rippleView.rippleInProgress()) {
             // Skip if ripple is still playing
             return
         }
@@ -231,7 +228,7 @@
 data class ChipReceiverInfo(
     val routeInfo: MediaRoute2Info,
     val appIconDrawableOverride: Drawable?,
-    val appNameOverride: CharSequence?
-) : TemporaryViewInfo {
-    override fun getTimeoutMs() = DEFAULT_TIMEOUT_MILLIS
-}
+    val appNameOverride: CharSequence?,
+    override val windowTitle: String = MediaTttUtils.WINDOW_TITLE_RECEIVER,
+    override val wakeReason: String = MediaTttUtils.WAKE_REASON_RECEIVER,
+) : TemporaryViewInfo()
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
index 6e596ee..af7317c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/ChipStateSender.kt
@@ -43,7 +43,7 @@
     @StringRes val stringResId: Int?,
     val transferStatus: TransferStatus,
     val endItem: SenderEndItem?,
-    val timeout: Long = DEFAULT_TIMEOUT_MILLIS
+    val timeout: Int = DEFAULT_TIMEOUT_MILLIS,
 ) {
     /**
      * A state representing that the two devices are close but not close enough to *start* a cast to
@@ -223,6 +223,6 @@
 // Give the Transfer*Triggered states a longer timeout since those states represent an active
 // process and we should keep the user informed about it as long as possible (but don't allow it to
 // continue indefinitely).
-private const val TRANSFER_TRIGGERED_TIMEOUT_MILLIS = 30000L
+private const val TRANSFER_TRIGGERED_TIMEOUT_MILLIS = 30000
 
 private const val TAG = "ChipStateSender"
diff --git a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
index 1fa8fae..d1ea2d0 100644
--- a/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinator.kt
@@ -159,6 +159,9 @@
                     }
                 },
             vibrationEffect = chipStateSender.transferStatus.vibrationEffect,
+            windowTitle = MediaTttUtils.WINDOW_TITLE_SENDER,
+            wakeReason = MediaTttUtils.WAKE_REASON_SENDER,
+            timeoutMs = chipStateSender.timeout,
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/motiontool/MotionToolModule.kt b/packages/SystemUI/src/com/android/systemui/motiontool/MotionToolModule.kt
new file mode 100644
index 0000000..1324d2c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/motiontool/MotionToolModule.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2022 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.motiontool
+
+import android.view.WindowManagerGlobal
+import com.android.app.motiontool.DdmHandleMotionTool
+import com.android.app.motiontool.MotionToolManager
+import com.android.app.viewcapture.ViewCapture
+import com.android.systemui.CoreStartable
+import dagger.Binds
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+
+@Module
+interface MotionToolModule {
+
+    companion object {
+
+        @Provides
+        fun provideDdmHandleMotionTool(motionToolManager: MotionToolManager): DdmHandleMotionTool {
+            return DdmHandleMotionTool.getInstance(motionToolManager)
+        }
+
+        @Provides
+        fun provideMotionToolManager(
+            viewCapture: ViewCapture,
+            windowManagerGlobal: WindowManagerGlobal
+        ): MotionToolManager {
+            return MotionToolManager.getInstance(viewCapture, windowManagerGlobal)
+        }
+
+        @Provides
+        fun provideWindowManagerGlobal(): WindowManagerGlobal = WindowManagerGlobal.getInstance()
+
+        @Provides fun provideViewCapture(): ViewCapture = ViewCapture.getInstance()
+    }
+
+    @Binds
+    @IntoMap
+    @ClassKey(MotionToolStartable::class)
+    fun bindMotionToolStartable(impl: MotionToolStartable): CoreStartable
+}
diff --git a/packages/SystemUI/src/com/android/systemui/motiontool/MotionToolStartable.kt b/packages/SystemUI/src/com/android/systemui/motiontool/MotionToolStartable.kt
new file mode 100644
index 0000000..fbb9538
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/motiontool/MotionToolStartable.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2022 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.motiontool
+
+import com.android.app.motiontool.DdmHandleMotionTool
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+
+@SysUISingleton
+class MotionToolStartable
+@Inject
+internal constructor(private val ddmHandleMotionTool: DdmHandleMotionTool) : CoreStartable {
+
+    override fun start() {
+        ddmHandleMotionTool.register()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 50cf63d..b9f5859 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -87,14 +87,18 @@
 import android.view.HapticFeedbackConstants;
 import android.view.InsetsFrameProvider;
 import android.view.InsetsState.InternalInsetsType;
-import android.view.InsetsVisibilities;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.Surface;
+import android.view.SurfaceControl;
+import android.view.SurfaceControl.Transaction;
 import android.view.View;
+import android.view.ViewRootImpl;
+import android.view.ViewRootImpl.SurfaceChangedCallback;
 import android.view.ViewTreeObserver;
 import android.view.ViewTreeObserver.InternalInsetsInfo;
 import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
+import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowInsetsController.Behavior;
 import android.view.WindowManager;
@@ -366,15 +370,6 @@
         }
 
         @Override
-        public void onQuickStepStarted() {
-            // Use navbar dragging as a signal to hide the rotate button
-            mView.getRotationButtonController().setRotateSuggestionButtonState(false);
-
-            // Hide the notifications panel when quick step starts
-            mShadeController.collapsePanel(true /* animate */);
-        }
-
-        @Override
         public void onPrioritizedRotation(@Surface.Rotation int rotation) {
             mStartingQuickSwitchRotation = rotation;
             if (rotation == -1) {
@@ -487,6 +482,24 @@
                 }
             };
 
+    private final ViewRootImpl.SurfaceChangedCallback mSurfaceChangedCallback =
+            new SurfaceChangedCallback() {
+            @Override
+            public void surfaceCreated(Transaction t) {
+                notifyNavigationBarSurface();
+            }
+
+            @Override
+            public void surfaceDestroyed() {
+                notifyNavigationBarSurface();
+            }
+
+            @Override
+            public void surfaceReplaced(Transaction t) {
+                notifyNavigationBarSurface();
+            }
+    };
+
     @Inject
     NavigationBar(
             NavigationBarView navigationBarView,
@@ -696,7 +709,8 @@
         final Display display = mView.getDisplay();
         mView.setComponents(mRecentsOptional);
         if (mCentralSurfacesOptionalLazy.get().isPresent()) {
-            mView.setComponents(mCentralSurfacesOptionalLazy.get().get().getPanelController());
+            mView.setComponents(
+                    mCentralSurfacesOptionalLazy.get().get().getNotificationPanelViewController());
         }
         mView.setDisabledFlags(mDisabledFlags1, mSysUiFlagsContainer);
         mView.setOnVerticalChangedListener(this::onVerticalChanged);
@@ -717,6 +731,8 @@
 
         mView.getViewTreeObserver().addOnComputeInternalInsetsListener(
                 mOnComputeInternalInsetsListener);
+        mView.getViewRootImpl().addSurfaceChangedCallback(mSurfaceChangedCallback);
+        notifyNavigationBarSurface();
 
         mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
 
@@ -795,6 +811,10 @@
         mHandler.removeCallbacks(mEnableLayoutTransitions);
         mNavBarHelper.removeNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
         mPipOptional.ifPresent(mView::removePipExclusionBoundsChangeListener);
+        ViewRootImpl viewRoot = mView.getViewRootImpl();
+        if (viewRoot != null) {
+            viewRoot.removeSurfaceChangedCallback(mSurfaceChangedCallback);
+        }
         mFrame = null;
         mOrientationHandle = null;
     }
@@ -947,6 +967,12 @@
         }
     }
 
+    private void notifyNavigationBarSurface() {
+        ViewRootImpl viewRoot = mView.getViewRootImpl();
+        SurfaceControl surface = viewRoot != null ? viewRoot.getSurfaceControl() : null;
+        mOverviewProxyService.onNavigationBarSurfaceChanged(surface);
+    }
+
     private int deltaRotation(int oldRotation, int newRotation) {
         int delta = newRotation - oldRotation;
         if (delta < 0) delta += 4;
@@ -1059,7 +1085,7 @@
     @Override
     public void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
             AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
-            @Behavior int behavior, InsetsVisibilities requestedVisibilities, String packageName,
+            @Behavior int behavior, @InsetsType int requestedVisibleTypes, String packageName,
             LetterboxDetails[] letterboxDetails) {
         if (displayId != mDisplayId) {
             return;
@@ -1272,8 +1298,8 @@
     }
 
     private void onVerticalChanged(boolean isVertical) {
-        mCentralSurfacesOptionalLazy.get().ifPresent(
-                statusBar -> statusBar.setQsScrimEnabled(!isVertical));
+        mCentralSurfacesOptionalLazy.get().ifPresent(statusBar ->
+                statusBar.getNotificationPanelViewController().setQsScrimEnabled(!isVertical));
     }
 
     private boolean onNavigationTouch(View v, MotionEvent event) {
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
index 73fc21e..eb87ff0 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/TaskbarDelegate.java
@@ -49,8 +49,8 @@
 import android.os.RemoteException;
 import android.util.Log;
 import android.view.Display;
-import android.view.InsetsVisibilities;
 import android.view.View;
+import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowInsetsController.Behavior;
 
@@ -355,7 +355,7 @@
     @Override
     public void onSystemBarAttributesChanged(int displayId, int appearance,
             AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme, int behavior,
-            InsetsVisibilities requestedVisibilities, String packageName,
+            @InsetsType int requestedVisibleTypes, String packageName,
             LetterboxDetails[] letterboxDetails) {
         mOverviewProxyService.onSystemBarAttributesChanged(displayId, behavior);
         boolean nbModeChanged = false;
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
index 622f5a2..83c2a5d 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/buttons/KeyButtonView.java
@@ -412,10 +412,6 @@
         logSomePresses(action, flags);
         if (mCode == KeyEvent.KEYCODE_BACK && flags != KeyEvent.FLAG_LONG_PRESS) {
             Log.i(TAG, "Back button event: " + KeyEvent.actionToString(action));
-            if (action == MotionEvent.ACTION_UP) {
-                mOverviewProxyService.notifyBackAction((flags & KeyEvent.FLAG_CANCELED) == 0,
-                        -1, -1, true /* isButton */, false /* gestureSwipeLeft */);
-            }
         }
         final int repeatCount = (flags & KeyEvent.FLAG_LONG_PRESS) != 0 ? 1 : 0;
         final KeyEvent ev = new KeyEvent(mDownTime, when, action, mCode, repeatCount,
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
index 10ff48b..7964d16 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/gestural/EdgeBackGestureHandler.java
@@ -289,8 +289,6 @@
                         mBackAnimation.setTriggerBack(true);
                     }
 
-                    mOverviewProxyService.notifyBackAction(true, (int) mDownPoint.x,
-                            (int) mDownPoint.y, false /* isButton */, !mIsOnLeftEdge);
                     logGesture(mInRejectedExclusion
                             ? SysUiStatsLog.BACK_GESTURE__TYPE__COMPLETED_REJECTED
                             : SysUiStatsLog.BACK_GESTURE__TYPE__COMPLETED);
@@ -302,8 +300,6 @@
                         mBackAnimation.setTriggerBack(false);
                     }
                     logGesture(SysUiStatsLog.BACK_GESTURE__TYPE__INCOMPLETE);
-                    mOverviewProxyService.notifyBackAction(false, (int) mDownPoint.x,
-                            (int) mDownPoint.y, false /* isButton */, !mIsOnLeftEdge);
                 }
 
                 @Override
@@ -804,9 +800,6 @@
 
         if (mExcludeRegion.contains(x, y)) {
             if (withinRange) {
-                // Log as exclusion only if it is in acceptable range in the first place.
-                mOverviewProxyService.notifyBackAction(
-                        false /* completed */, -1, -1, false /* isButton */, !mIsOnLeftEdge);
                 // We don't have the end point for logging purposes.
                 mEndPoint.x = -1;
                 mEndPoint.y = -1;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java
index 703b95a..b5ceeae 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/carrier/QSCarrier.java
@@ -19,6 +19,7 @@
 import android.annotation.StyleRes;
 import android.content.Context;
 import android.content.res.ColorStateList;
+import android.content.res.Configuration;
 import android.text.TextUtils;
 import android.util.AttributeSet;
 import android.view.View;
@@ -33,6 +34,7 @@
 import com.android.settingslib.graph.SignalDrawable;
 import com.android.systemui.FontSizeUtils;
 import com.android.systemui.R;
+import com.android.systemui.util.LargeScreenUtils;
 
 import java.util.Objects;
 
@@ -72,6 +74,7 @@
         mMobileSignal = findViewById(R.id.mobile_signal);
         mCarrierText = findViewById(R.id.qs_carrier_text);
         mSpacer = findViewById(R.id.spacer);
+        updateResources();
     }
 
     /**
@@ -142,4 +145,20 @@
     public void updateTextAppearance(@StyleRes int resId) {
         FontSizeUtils.updateFontSizeFromStyle(mCarrierText, resId);
     }
+
+    @Override
+    protected void onConfigurationChanged(Configuration newConfig) {
+        super.onConfigurationChanged(newConfig);
+        updateResources();
+    }
+
+    private void updateResources() {
+        boolean useLargeScreenHeader =
+                LargeScreenUtils.shouldUseLargeScreenShadeHeader(getResources());
+        mCarrierText.setMaxEms(
+                useLargeScreenHeader
+                        ? Integer.MAX_VALUE
+                        : getResources().getInteger(R.integer.qs_carrier_max_em)
+        );
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
index e9a6c25..1f92b12 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSIconViewImpl.java
@@ -140,7 +140,7 @@
             iv.setTag(R.id.qs_icon_tag, icon);
             iv.setTag(R.id.qs_slash_tag, state.slash);
             iv.setPadding(0, padding, 0, padding);
-            if (d instanceof Animatable2) {
+            if (shouldAnimate && d instanceof Animatable2) {
                 Animatable2 a = (Animatable2) d;
                 a.start();
                 if (state.isTransient) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
index 66be00d..46c4f41 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyService.java
@@ -64,6 +64,7 @@
 import android.view.KeyEvent;
 import android.view.MotionEvent;
 import android.view.Surface;
+import android.view.SurfaceControl;
 import android.view.accessibility.AccessibilityManager;
 import android.view.inputmethod.InputMethodManager;
 
@@ -149,6 +150,7 @@
     private final UiEventLogger mUiEventLogger;
 
     private Region mActiveNavBarRegion;
+    private SurfaceControl mNavigationBarSurface;
 
     private IOverviewProxy mOverviewProxy;
     private int mConnectionBackoffAttempts;
@@ -190,7 +192,8 @@
                 // TODO move this logic to message queue
                 mCentralSurfacesOptionalLazy.get().ifPresent(centralSurfaces -> {
                     if (event.getActionMasked() == ACTION_DOWN) {
-                        centralSurfaces.getPanelController().startExpandLatencyTracking();
+                        centralSurfaces.getNotificationPanelViewController()
+                                        .startExpandLatencyTracking();
                     }
                     mHandler.post(() -> {
                         int action = event.getActionMasked();
@@ -217,17 +220,15 @@
         }
 
         @Override
-        public void onBackPressed() throws RemoteException {
+        public void onBackPressed() {
             verifyCallerAndClearCallingIdentityPostMain("onBackPressed", () -> {
                 sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK);
                 sendEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_BACK);
-
-                notifyBackAction(true, -1, -1, true, false);
             });
         }
 
         @Override
-        public void onImeSwitcherPressed() throws RemoteException {
+        public void onImeSwitcherPressed() {
             // TODO(b/204901476) We're intentionally using DEFAULT_DISPLAY for now since
             // Launcher/Taskbar isn't display aware.
             mContext.getSystemService(InputMethodManager.class)
@@ -316,12 +317,6 @@
         }
 
         @Override
-        public void notifySwipeUpGestureStarted() {
-            verifyCallerAndClearCallingIdentityPostMain("notifySwipeUpGestureStarted", () ->
-                    notifySwipeUpGestureStartedInternal());
-        }
-
-        @Override
         public void notifyPrioritizedRotation(@Surface.Rotation int rotation) {
             verifyCallerAndClearCallingIdentityPostMain("notifyPrioritizedRotation", () ->
                     notifyPrioritizedRotationInternal(rotation));
@@ -443,6 +438,7 @@
                 Log.e(TAG_OPS, "Failed to call onInitialize()", e);
             }
             dispatchNavButtonBounds();
+            dispatchNavigationBarSurface();
 
             // Force-update the systemui state flags
             updateSystemUiStateFlags();
@@ -597,11 +593,18 @@
                 .commitUpdate(mContext.getDisplayId());
     }
 
-    public void notifyBackAction(boolean completed, int downX, int downY, boolean isButton,
-            boolean gestureSwipeLeft) {
+    /**
+     * Called when the navigation bar surface is created or changed
+     */
+    public void onNavigationBarSurfaceChanged(SurfaceControl navbarSurface) {
+        mNavigationBarSurface = navbarSurface;
+        dispatchNavigationBarSurface();
+    }
+
+    private void dispatchNavigationBarSurface() {
         try {
             if (mOverviewProxy != null) {
-                mOverviewProxy.onBackAction(completed, downX, downY, isButton, gestureSwipeLeft);
+                mOverviewProxy.onNavigationBarSurface(mNavigationBarSurface);
             }
         } catch (RemoteException e) {
             Log.e(TAG_OPS, "Failed to notify back action", e);
@@ -614,7 +617,7 @@
         final NavigationBarView navBarView =
                 mNavBarControllerLazy.get().getNavigationBarView(mContext.getDisplayId());
         final NotificationPanelViewController panelController =
-                mCentralSurfacesOptionalLazy.get().get().getPanelController();
+                mCentralSurfacesOptionalLazy.get().get().getNotificationPanelViewController();
         if (SysUiState.DEBUG) {
             Log.d(TAG_OPS, "Updating sysui state flags: navBarFragment=" + navBarFragment
                     + " navBarView=" + navBarView + " panelController=" + panelController);
@@ -800,24 +803,12 @@
         }
     }
 
-    public void notifyQuickStepStarted() {
-        for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
-            mConnectionCallbacks.get(i).onQuickStepStarted();
-        }
-    }
-
     private void notifyPrioritizedRotationInternal(@Surface.Rotation int rotation) {
         for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
             mConnectionCallbacks.get(i).onPrioritizedRotation(rotation);
         }
     }
 
-    public void notifyQuickScrubStarted() {
-        for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
-            mConnectionCallbacks.get(i).onQuickScrubStarted();
-        }
-    }
-
     private void notifyAssistantProgress(@FloatRange(from = 0.0, to = 1.0) float progress) {
         for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
             mConnectionCallbacks.get(i).onAssistantProgress(progress);
@@ -836,12 +827,6 @@
         }
     }
 
-    private void notifySwipeUpGestureStartedInternal() {
-        for (int i = mConnectionCallbacks.size() - 1; i >= 0; --i) {
-            mConnectionCallbacks.get(i).onSwipeUpGestureStarted();
-        }
-    }
-
     public void notifyAssistantVisibilityChanged(float visibility) {
         try {
             if (mOverviewProxy != null) {
@@ -1005,23 +990,20 @@
         pw.print("  mWindowCornerRadius="); pw.println(mWindowCornerRadius);
         pw.print("  mSupportsRoundedCornersOnWindows="); pw.println(mSupportsRoundedCornersOnWindows);
         pw.print("  mActiveNavBarRegion="); pw.println(mActiveNavBarRegion);
+        pw.print("  mNavigationBarSurface="); pw.println(mNavigationBarSurface);
         pw.print("  mNavBarMode="); pw.println(mNavBarMode);
         mSysUiState.dump(pw, args);
     }
 
     public interface OverviewProxyListener {
         default void onConnectionChanged(boolean isConnected) {}
-        default void onQuickStepStarted() {}
-        default void onSwipeUpGestureStarted() {}
         default void onPrioritizedRotation(@Surface.Rotation int rotation) {}
         default void onOverviewShown(boolean fromHome) {}
-        default void onQuickScrubStarted() {}
         /** Notify the recents app (overview) is started by 3-button navigation. */
         default void onToggleRecentApps() {}
         default void onHomeRotationEnabled(boolean enabled) {}
         default void onTaskbarStatusUpdated(boolean visible, boolean stashed) {}
         default void onTaskbarAutohideSuspend(boolean suspend) {}
-        default void onSystemUiStateChanged(int sysuiStateFlags) {}
         default void onAssistantProgress(@FloatRange(from = 0.0, to = 1.0) float progress) {}
         default void onAssistantGestureCompletion(float velocity) {}
         default void startAssistant(Bundle bundle) {}
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/MultiRippleController.kt b/packages/SystemUI/src/com/android/systemui/ripple/MultiRippleController.kt
new file mode 100644
index 0000000..48df15c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ripple/MultiRippleController.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 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.ripple
+
+import androidx.annotation.VisibleForTesting
+
+/** Controller that handles playing [RippleAnimation]. */
+class MultiRippleController(private val multipleRippleView: MultiRippleView) {
+
+    companion object {
+        /** Max number of ripple animations at a time. */
+        @VisibleForTesting const val MAX_RIPPLE_NUMBER = 10
+    }
+
+    /** Updates all the ripple colors during the animation. */
+    fun updateColor(color: Int) {
+        multipleRippleView.ripples.forEach { anim -> anim.updateColor(color) }
+    }
+
+    fun play(rippleAnimation: RippleAnimation) {
+        if (multipleRippleView.ripples.size >= MAX_RIPPLE_NUMBER) {
+            return
+        }
+
+        multipleRippleView.ripples.add(rippleAnimation)
+
+        // Remove ripple once the animation is done
+        rippleAnimation.play { multipleRippleView.ripples.remove(rippleAnimation) }
+
+        // Trigger drawing
+        multipleRippleView.invalidate()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/MultiRippleView.kt b/packages/SystemUI/src/com/android/systemui/ripple/MultiRippleView.kt
new file mode 100644
index 0000000..c7f0b7e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ripple/MultiRippleView.kt
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 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.ripple
+
+import android.content.Context
+import android.graphics.Canvas
+import android.graphics.Paint
+import android.util.AttributeSet
+import android.util.Log
+import android.view.View
+
+/**
+ * A view that allows multiple ripples to play.
+ *
+ * Use [MultiRippleController] to play ripple animations.
+ */
+class MultiRippleView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
+
+    internal val ripples = ArrayList<RippleAnimation>()
+    private val ripplePaint = Paint()
+    private var isWarningLogged = false
+
+    companion object {
+        const val TAG = "MultiRippleView"
+    }
+
+    override fun onDraw(canvas: Canvas?) {
+        if (canvas == null || !canvas.isHardwareAccelerated) {
+            // Drawing with the ripple shader requires hardware acceleration, so skip
+            // if it's unsupported.
+            if (!isWarningLogged) {
+                // Only log once to not spam.
+                Log.w(
+                    TAG,
+                    "Can't draw ripple shader. $canvas does not support hardware acceleration."
+                )
+                isWarningLogged = true
+            }
+            return
+        }
+
+        var shouldInvalidate = false
+
+        ripples.forEach { anim ->
+            ripplePaint.shader = anim.rippleShader
+            canvas.drawPaint(ripplePaint)
+
+            shouldInvalidate = shouldInvalidate || anim.isPlaying()
+        }
+
+        if (shouldInvalidate) invalidate()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleAnimation.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleAnimation.kt
new file mode 100644
index 0000000..aca9e25
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ripple/RippleAnimation.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2022 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.ripple
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import androidx.core.graphics.ColorUtils
+
+/** A single ripple animation. */
+class RippleAnimation(private val config: RippleAnimationConfig) {
+    internal val rippleShader: RippleShader = RippleShader(config.rippleShape)
+    private val animator: ValueAnimator = ValueAnimator.ofFloat(0f, 1f)
+
+    init {
+        applyConfigToShader()
+    }
+
+    /** Updates the ripple color during the animation. */
+    fun updateColor(color: Int) {
+        config.apply { config.color = color }
+        applyConfigToShader()
+    }
+
+    @JvmOverloads
+    fun play(onAnimationEnd: Runnable? = null) {
+        if (isPlaying()) {
+            return // Ignore if ripple effect is already playing
+        }
+
+        animator.duration = config.duration
+        animator.addUpdateListener { updateListener ->
+            val now = updateListener.currentPlayTime
+            val progress = updateListener.animatedValue as Float
+            rippleShader.progress = progress
+            rippleShader.distortionStrength = if (config.shouldDistort) 1 - progress else 0f
+            rippleShader.time = now.toFloat()
+        }
+        animator.addListener(
+            object : AnimatorListenerAdapter() {
+                override fun onAnimationEnd(animation: Animator?) {
+                    onAnimationEnd?.run()
+                }
+            }
+        )
+        animator.start()
+    }
+
+    /** Indicates whether the animation is playing. */
+    fun isPlaying(): Boolean = animator.isRunning
+
+    private fun applyConfigToShader() {
+        rippleShader.setCenter(config.centerX, config.centerY)
+        rippleShader.setMaxSize(config.maxWidth, config.maxHeight)
+        rippleShader.rippleFill = config.shouldFillRipple
+        rippleShader.pixelDensity = config.pixelDensity
+        rippleShader.color = ColorUtils.setAlphaComponent(config.color, config.opacity)
+        rippleShader.sparkleStrength = config.sparkleStrength
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleAnimationConfig.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleAnimationConfig.kt
new file mode 100644
index 0000000..8812254
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/ripple/RippleAnimationConfig.kt
@@ -0,0 +1,32 @@
+package com.android.systemui.ripple
+
+import android.graphics.Color
+
+/**
+ * A struct that holds the ripple animation configurations.
+ *
+ * <p>This configuration is designed to play a SINGLE animation. Do not reuse or modify the
+ * configuration parameters to play different animations, unless the value has to change within the
+ * single animation (e.g. Change color or opacity during the animation). Note that this data class
+ * is pulled out to make the [RippleAnimation] constructor succinct.
+ */
+data class RippleAnimationConfig(
+    val rippleShape: RippleShader.RippleShape = RippleShader.RippleShape.CIRCLE,
+    val duration: Long = 0L,
+    val centerX: Float = 0f,
+    val centerY: Float = 0f,
+    val maxWidth: Float = 0f,
+    val maxHeight: Float = 0f,
+    val pixelDensity: Float = 1f,
+    var color: Int = Color.WHITE,
+    val opacity: Int = RIPPLE_DEFAULT_ALPHA,
+    val shouldFillRipple: Boolean = false,
+    val sparkleStrength: Float = RIPPLE_SPARKLE_STRENGTH,
+    val shouldDistort: Boolean = true
+) {
+    companion object {
+        const val RIPPLE_SPARKLE_STRENGTH: Float = 0.3f
+        const val RIPPLE_DEFAULT_COLOR: Int = 0xffffffff.toInt()
+        const val RIPPLE_DEFAULT_ALPHA: Int = 45 // full opacity is 255.
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt b/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt
index 1e51ffa..a6d7930 100644
--- a/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt
+++ b/packages/SystemUI/src/com/android/systemui/ripple/RippleView.kt
@@ -28,10 +28,6 @@
 import androidx.core.graphics.ColorUtils
 import com.android.systemui.ripple.RippleShader.RippleShape
 
-private const val RIPPLE_SPARKLE_STRENGTH: Float = 0.3f
-private const val RIPPLE_DEFAULT_COLOR: Int = 0xffffffff.toInt()
-const val RIPPLE_DEFAULT_ALPHA: Int = 45
-
 /**
  * A generic expanding ripple effect.
  *
@@ -45,8 +41,8 @@
         private set
 
     private val ripplePaint = Paint()
+    private val animator = ValueAnimator.ofFloat(0f, 1f)
 
-    var rippleInProgress: Boolean = false
     var duration: Long = 1750
 
     private var maxWidth: Float = 0.0f
@@ -80,9 +76,9 @@
         this.rippleShape = rippleShape
         rippleShader = RippleShader(rippleShape)
 
-        rippleShader.color = RIPPLE_DEFAULT_COLOR
+        rippleShader.color = RippleAnimationConfig.RIPPLE_DEFAULT_COLOR
         rippleShader.progress = 0f
-        rippleShader.sparkleStrength = RIPPLE_SPARKLE_STRENGTH
+        rippleShader.sparkleStrength = RippleAnimationConfig.RIPPLE_SPARKLE_STRENGTH
         rippleShader.pixelDensity = resources.displayMetrics.density
 
         ripplePaint.shader = rippleShader
@@ -90,10 +86,9 @@
 
     @JvmOverloads
     fun startRipple(onAnimationEnd: Runnable? = null) {
-        if (rippleInProgress) {
+        if (animator.isRunning) {
             return // Ignore if ripple effect is already playing
         }
-        val animator = ValueAnimator.ofFloat(0f, 1f)
         animator.duration = duration
         animator.addUpdateListener { updateListener ->
             val now = updateListener.currentPlayTime
@@ -105,19 +100,17 @@
         }
         animator.addListener(object : AnimatorListenerAdapter() {
             override fun onAnimationEnd(animation: Animator?) {
-                rippleInProgress = false
                 onAnimationEnd?.run()
             }
         })
         animator.start()
-        rippleInProgress = true
     }
 
     /** Set the color to be used for the ripple.
      *
      * The alpha value of the color will be applied to the ripple. The alpha range is [0-100].
      */
-    fun setColor(color: Int, alpha: Int = RIPPLE_DEFAULT_ALPHA) {
+    fun setColor(color: Int, alpha: Int = RippleAnimationConfig.RIPPLE_DEFAULT_ALPHA) {
         rippleShader.color = ColorUtils.setAlphaComponent(color, alpha)
     }
 
@@ -137,6 +130,9 @@
         rippleShader.sparkleStrength = strength
     }
 
+    /** Indicates whether the ripple animation is playing. */
+    fun rippleInProgress(): Boolean = animator.isRunning
+
     override fun onDraw(canvas: Canvas?) {
         if (canvas == null || !canvas.isHardwareAccelerated) {
             // Drawing with the ripple shader requires hardware acceleration, so skip
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
index e3658de..c8c1337 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ImageExporter.java
@@ -38,6 +38,8 @@
 import androidx.exifinterface.media.ExifInterface;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.Flags;
 
 import com.google.common.util.concurrent.ListenableFuture;
 
@@ -85,10 +87,12 @@
     private final ContentResolver mResolver;
     private CompressFormat mCompressFormat = CompressFormat.PNG;
     private int mQuality = 100;
+    private final FeatureFlags mFlags;
 
     @Inject
-    ImageExporter(ContentResolver resolver) {
+    ImageExporter(ContentResolver resolver, FeatureFlags flags) {
         mResolver = resolver;
+        mFlags = flags;
     }
 
     /**
@@ -161,7 +165,7 @@
             ZonedDateTime captureTime, UserHandle owner) {
 
         final Task task = new Task(mResolver, requestId, bitmap, captureTime, mCompressFormat,
-                mQuality, /* publish */ true, owner);
+                mQuality, /* publish */ true, owner, mFlags);
 
         return CallbackToFutureAdapter.getFuture(
                 (completer) -> {
@@ -209,9 +213,11 @@
         private final UserHandle mOwner;
         private final String mFileName;
         private final boolean mPublish;
+        private final FeatureFlags mFlags;
 
         Task(ContentResolver resolver, UUID requestId, Bitmap bitmap, ZonedDateTime captureTime,
-                CompressFormat format, int quality, boolean publish, UserHandle owner) {
+                CompressFormat format, int quality, boolean publish, UserHandle owner,
+                FeatureFlags flags) {
             mResolver = resolver;
             mRequestId = requestId;
             mBitmap = bitmap;
@@ -221,6 +227,7 @@
             mOwner = owner;
             mFileName = createFilename(mCaptureTime, mFormat);
             mPublish = publish;
+            mFlags = flags;
         }
 
         public Result execute() throws ImageExportException, InterruptedException {
@@ -234,7 +241,7 @@
                     start = Instant.now();
                 }
 
-                uri = createEntry(mResolver, mFormat, mCaptureTime, mFileName, mOwner);
+                uri = createEntry(mResolver, mFormat, mCaptureTime, mFileName, mOwner, mFlags);
                 throwIfInterrupted();
 
                 writeImage(mResolver, mBitmap, mFormat, mQuality, uri);
@@ -278,13 +285,15 @@
     }
 
     private static Uri createEntry(ContentResolver resolver, CompressFormat format,
-            ZonedDateTime time, String fileName, UserHandle owner) throws ImageExportException {
+            ZonedDateTime time, String fileName, UserHandle owner, FeatureFlags flags)
+            throws ImageExportException {
         Trace.beginSection("ImageExporter_createEntry");
         try {
             final ContentValues values = createMetadata(time, format, fileName);
 
             Uri baseUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
-            if (UserHandle.myUserId() != owner.getIdentifier()) {
+            if (flags.isEnabled(Flags.SCREENSHOT_WORK_PROFILE_POLICY)
+                    && UserHandle.myUserId() != owner.getIdentifier()) {
                 baseUri = ContentProvider.maybeAddUserId(baseUri, owner.getIdentifier());
             }
             Uri uri = resolver.insert(baseUri, values);
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
index d524a35..9b5295d 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/ScreenshotController.java
@@ -1043,8 +1043,13 @@
     }
 
     private boolean isUserSetupComplete(UserHandle owner) {
-        return Settings.Secure.getInt(mContext.createContextAsUser(owner, 0)
-                .getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1;
+        if (mFlags.isEnabled(SCREENSHOT_WORK_PROFILE_POLICY)) {
+            return Settings.Secure.getInt(mContext.createContextAsUser(owner, 0)
+                    .getContentResolver(), SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1;
+        } else {
+            return Settings.Secure.getInt(mContext.getContentResolver(),
+                    SETTINGS_SECURE_USER_SETUP_COMPLETE, 0) == 1;
+        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvSensorPrivacyChangedActivity.java b/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvSensorPrivacyChangedActivity.java
new file mode 100644
index 0000000..731b177
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvSensorPrivacyChangedActivity.java
@@ -0,0 +1,233 @@
+/*
+ * Copyright (C) 2022 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.sensorprivacy.television;
+
+import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
+import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE;
+
+import android.annotation.DimenRes;
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.graphics.drawable.Animatable;
+import android.graphics.drawable.Drawable;
+import android.hardware.SensorPrivacyManager;
+import android.os.Bundle;
+import android.provider.Settings;
+import android.util.Log;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.Button;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import com.android.systemui.R;
+import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
+import com.android.systemui.tv.TvBottomSheetActivity;
+import com.android.systemui.util.settings.GlobalSettings;
+
+import javax.inject.Inject;
+
+/**
+ * Bottom sheet that is shown when the camera/mic sensors privacy state changed
+ * by the global software toggle or physical privacy switch.
+ */
+public class TvSensorPrivacyChangedActivity extends TvBottomSheetActivity {
+
+    private static final String TAG = TvSensorPrivacyChangedActivity.class.getSimpleName();
+
+    private static final int ALL_SENSORS = Integer.MAX_VALUE;
+
+    private int mSensor = -1;
+    private int mToggleType = -1;
+
+    private final GlobalSettings mGlobalSettings;
+    private final IndividualSensorPrivacyController mSensorPrivacyController;
+    private IndividualSensorPrivacyController.Callback mSensorPrivacyCallback;
+    private TextView mTitle;
+    private TextView mContent;
+    private ImageView mIcon;
+    private ImageView mSecondIcon;
+    private Button mPositiveButton;
+    private Button mCancelButton;
+
+    @Inject
+    public TvSensorPrivacyChangedActivity(
+            IndividualSensorPrivacyController individualSensorPrivacyController,
+            GlobalSettings globalSettings) {
+        mSensorPrivacyController = individualSensorPrivacyController;
+        mGlobalSettings = globalSettings;
+    }
+
+    @Override
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        getWindow().addSystemFlags(
+                WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
+
+        boolean allSensors = getIntent().getBooleanExtra(SensorPrivacyManager.EXTRA_ALL_SENSORS,
+                false);
+        if (allSensors) {
+            mSensor = ALL_SENSORS;
+        } else {
+            mSensor = getIntent().getIntExtra(SensorPrivacyManager.EXTRA_SENSOR, -1);
+        }
+
+        mToggleType = getIntent().getIntExtra(SensorPrivacyManager.EXTRA_TOGGLE_TYPE, -1);
+
+        if (mSensor == -1 || mToggleType == -1) {
+            Log.v(TAG, "Invalid extras");
+            finish();
+            return;
+        }
+
+        // Do not show for software toggles
+        if (mToggleType == SensorPrivacyManager.TOGGLE_TYPE_SOFTWARE) {
+            finish();
+            return;
+        }
+
+        mSensorPrivacyCallback = (sensor, blocked) -> {
+            updateUI();
+        };
+
+        initUI();
+    }
+
+    private void initUI() {
+        mTitle = findViewById(R.id.bottom_sheet_title);
+        mContent = findViewById(R.id.bottom_sheet_body);
+        mIcon = findViewById(R.id.bottom_sheet_icon);
+        // mic icon if both icons are shown
+        mSecondIcon = findViewById(R.id.bottom_sheet_second_icon);
+        mPositiveButton = findViewById(R.id.bottom_sheet_positive_button);
+        mCancelButton = findViewById(R.id.bottom_sheet_negative_button);
+
+        mCancelButton.setText(android.R.string.cancel);
+        mCancelButton.setOnClickListener(v -> finish());
+
+        updateUI();
+    }
+
+    private void updateUI() {
+        final Resources resources = getResources();
+        setIconTint(resources.getBoolean(R.bool.config_unblockHwSensorIconEnableTint));
+        setIconSize(R.dimen.unblock_hw_sensor_icon_width, R.dimen.unblock_hw_sensor_icon_height);
+
+        switch (mSensor) {
+            case CAMERA:
+                updateUiForCameraUpdate(
+                        mSensorPrivacyController.isSensorBlockedByHardwareToggle(CAMERA));
+                break;
+            case MICROPHONE:
+            default:
+                updateUiForMicUpdate(
+                        mSensorPrivacyController.isSensorBlockedByHardwareToggle(MICROPHONE));
+                break;
+        }
+
+        // Start animation if drawable is animated
+        Drawable iconDrawable = mIcon.getDrawable();
+        if (iconDrawable instanceof Animatable) {
+            ((Animatable) iconDrawable).start();
+        }
+
+        mPositiveButton.setVisibility(View.GONE);
+        mCancelButton.setText(android.R.string.ok);
+    }
+
+    private void updateUiForMicUpdate(boolean blocked) {
+        if (blocked) {
+            mTitle.setText(R.string.sensor_privacy_mic_turned_off_dialog_title);
+            if (isExplicitUserInteractionAudioBypassAllowed()) {
+                mContent.setText(R.string.sensor_privacy_mic_blocked_with_exception_dialog_content);
+            } else {
+                mContent.setText(R.string.sensor_privacy_mic_blocked_no_exception_dialog_content);
+            }
+            mIcon.setImageResource(R.drawable.unblock_hw_sensor_microphone);
+            mSecondIcon.setVisibility(View.GONE);
+        } else {
+            mTitle.setText(R.string.sensor_privacy_mic_turned_on_dialog_title);
+            mContent.setText(R.string.sensor_privacy_mic_unblocked_dialog_content);
+            mIcon.setImageResource(com.android.internal.R.drawable.ic_mic_allowed);
+            mSecondIcon.setVisibility(View.GONE);
+        }
+    }
+
+    private void updateUiForCameraUpdate(boolean blocked) {
+        if (blocked) {
+            mTitle.setText(R.string.sensor_privacy_camera_turned_off_dialog_title);
+            mContent.setText(R.string.sensor_privacy_camera_blocked_dialog_content);
+            mIcon.setImageResource(R.drawable.unblock_hw_sensor_camera);
+            mSecondIcon.setVisibility(View.GONE);
+        } else {
+            mTitle.setText(R.string.sensor_privacy_camera_turned_on_dialog_title);
+            mContent.setText(R.string.sensor_privacy_camera_unblocked_dialog_content);
+            mIcon.setImageResource(com.android.internal.R.drawable.ic_camera_allowed);
+            mSecondIcon.setVisibility(View.GONE);
+        }
+    }
+
+    private void setIconTint(boolean enableTint) {
+        final Resources resources = getResources();
+
+        if (enableTint) {
+            final ColorStateList iconTint = resources.getColorStateList(
+                    R.color.bottom_sheet_icon_color, getTheme());
+            mIcon.setImageTintList(iconTint);
+            mSecondIcon.setImageTintList(iconTint);
+        } else {
+            mIcon.setImageTintList(null);
+            mSecondIcon.setImageTintList(null);
+        }
+
+        mIcon.invalidate();
+        mSecondIcon.invalidate();
+    }
+
+    private void setIconSize(@DimenRes int widthRes, @DimenRes int heightRes) {
+        final Resources resources = getResources();
+        final int iconWidth = resources.getDimensionPixelSize(widthRes);
+        final int iconHeight = resources.getDimensionPixelSize(heightRes);
+
+        mIcon.getLayoutParams().width = iconWidth;
+        mIcon.getLayoutParams().height = iconHeight;
+        mIcon.invalidate();
+
+        mSecondIcon.getLayoutParams().width = iconWidth;
+        mSecondIcon.getLayoutParams().height = iconHeight;
+        mSecondIcon.invalidate();
+    }
+
+    private boolean isExplicitUserInteractionAudioBypassAllowed() {
+        return mGlobalSettings.getInt(
+                Settings.Global.RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO_ENABLED, 1) == 1;
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        updateUI();
+        mSensorPrivacyController.addCallback(mSensorPrivacyCallback);
+    }
+
+    @Override
+    public void onPause() {
+        mSensorPrivacyController.removeCallback(mSensorPrivacyCallback);
+        super.onPause();
+    }
+
+}
diff --git a/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvUnblockSensorActivity.java b/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvUnblockSensorActivity.java
index d543eb2..1b9657f 100644
--- a/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvUnblockSensorActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/sensorprivacy/television/TvUnblockSensorActivity.java
@@ -16,17 +16,23 @@
 
 package com.android.systemui.sensorprivacy.television;
 
+import static android.content.pm.PackageManager.MATCH_SYSTEM_ONLY;
 import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
 import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE;
 import static android.hardware.SensorPrivacyManager.Sources.OTHER;
 
 import android.annotation.DimenRes;
+import android.app.AppOpsManager;
+import android.app.role.RoleManager;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
 import android.graphics.drawable.Animatable;
 import android.graphics.drawable.Drawable;
 import android.hardware.SensorPrivacyManager;
 import android.os.Bundle;
+import android.os.UserHandle;
 import android.util.Log;
 import android.view.View;
 import android.view.WindowManager;
@@ -39,6 +45,8 @@
 import com.android.systemui.statusbar.policy.IndividualSensorPrivacyController;
 import com.android.systemui.tv.TvBottomSheetActivity;
 
+import java.util.List;
+
 import javax.inject.Inject;
 
 /**
@@ -57,6 +65,8 @@
 
     private int mSensor = -1;
 
+    private final AppOpsManager mAppOpsManager;
+    private final RoleManager mRoleManager;
     private final IndividualSensorPrivacyController mSensorPrivacyController;
     private IndividualSensorPrivacyController.Callback mSensorPrivacyCallback;
     private TextView mTitle;
@@ -68,8 +78,11 @@
 
     @Inject
     public TvUnblockSensorActivity(
-            IndividualSensorPrivacyController individualSensorPrivacyController) {
+            IndividualSensorPrivacyController individualSensorPrivacyController,
+            AppOpsManager appOpsManager, RoleManager roleManager) {
         mSensorPrivacyController = individualSensorPrivacyController;
+        mAppOpsManager = appOpsManager;
+        mRoleManager = roleManager;
     }
 
     @Override
@@ -120,6 +133,10 @@
                 toastMsgResId = R.string.sensor_privacy_mic_camera_unblocked_toast_content;
                 break;
         }
+        showToastAndFinish(toastMsgResId);
+    }
+
+    private void showToastAndFinish(int toastMsgResId) {
         Toast.makeText(this, toastMsgResId, Toast.LENGTH_SHORT).show();
         finish();
     }
@@ -149,7 +166,9 @@
     }
 
     private void updateUI() {
-        if (isBlockedByHardwareToggle()) {
+        if (isHTTAccessDisabled()) {
+            updateUiForHTT();
+        } else if (isBlockedByHardwareToggle()) {
             updateUiForHardwareToggle();
         } else {
             updateUiForSoftwareToggle();
@@ -208,20 +227,20 @@
 
         switch (mSensor) {
             case MICROPHONE:
-                mTitle.setText(R.string.sensor_privacy_start_use_mic_dialog_title);
+                mTitle.setText(R.string.sensor_privacy_start_use_mic_blocked_dialog_title);
                 mContent.setText(R.string.sensor_privacy_start_use_mic_dialog_content);
                 mIcon.setImageResource(com.android.internal.R.drawable.perm_group_microphone);
                 mSecondIcon.setVisibility(View.GONE);
                 break;
             case CAMERA:
-                mTitle.setText(R.string.sensor_privacy_start_use_camera_dialog_title);
+                mTitle.setText(R.string.sensor_privacy_start_use_camera_blocked_dialog_title);
                 mContent.setText(R.string.sensor_privacy_start_use_camera_dialog_content);
                 mIcon.setImageResource(com.android.internal.R.drawable.perm_group_camera);
                 mSecondIcon.setVisibility(View.GONE);
                 break;
             case ALL_SENSORS:
             default:
-                mTitle.setText(R.string.sensor_privacy_start_use_mic_camera_dialog_title);
+                mTitle.setText(R.string.sensor_privacy_start_use_mic_camera_blocked_dialog_title);
                 mContent.setText(R.string.sensor_privacy_start_use_mic_camera_dialog_content);
                 mIcon.setImageResource(com.android.internal.R.drawable.perm_group_camera);
                 mSecondIcon.setImageResource(
@@ -241,6 +260,29 @@
         });
     }
 
+    private void updateUiForHTT() {
+        setIconTint(true);
+        setIconSize(R.dimen.bottom_sheet_icon_size, R.dimen.bottom_sheet_icon_size);
+
+        mTitle.setText(R.string.sensor_privacy_start_use_mic_blocked_dialog_title);
+        mContent.setText(R.string.sensor_privacy_htt_blocked_dialog_content);
+        mIcon.setImageResource(com.android.internal.R.drawable.perm_group_microphone);
+        mSecondIcon.setVisibility(View.GONE);
+
+        mPositiveButton.setText(R.string.sensor_privacy_dialog_open_settings);
+        mPositiveButton.setOnClickListener(v -> {
+            Intent openPrivacySettings = new Intent(ACTION_MANAGE_MICROPHONE_PRIVACY);
+            ActivityInfo activityInfo = openPrivacySettings.resolveActivityInfo(getPackageManager(),
+                    MATCH_SYSTEM_ONLY);
+            if (activityInfo == null) {
+                showToastAndFinish(com.android.internal.R.string.noApplications);
+            } else {
+                startActivity(openPrivacySettings);
+                finish();
+            }
+        });
+    }
+
     private void setIconTint(boolean enableTint) {
         final Resources resources = getResources();
 
@@ -272,6 +314,18 @@
         mSecondIcon.invalidate();
     }
 
+    private boolean isHTTAccessDisabled() {
+        String pkg = getIntent().getStringExtra(Intent.EXTRA_PACKAGE_NAME);
+        List<String> assistantPkgs = mRoleManager.getRoleHolders(RoleManager.ROLE_ASSISTANT);
+        if (!assistantPkgs.contains(pkg)) {
+            return false;
+        }
+
+        return (mAppOpsManager.checkOpNoThrow(
+                AppOpsManager.OP_RECEIVE_EXPLICIT_USER_INTERACTION_AUDIO, UserHandle.myUserId(),
+                pkg) != AppOpsManager.MODE_ALLOWED);
+    }
+
     @Override
     public void onResume() {
         super.onResume();
diff --git a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
index 6711734..cd5647e 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/settings/UserTrackerImpl.kt
@@ -30,7 +30,6 @@
 import androidx.annotation.WorkerThread
 import com.android.systemui.Dumpable
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.people.widget.PeopleSpaceWidgetProvider.EXTRA_USER_HANDLE
 import com.android.systemui.util.Assert
 import java.io.PrintWriter
 import java.lang.ref.WeakReference
@@ -53,7 +52,7 @@
  *
  * Class constructed and initialized in [SettingsModule].
  */
-class UserTrackerImpl internal constructor(
+open class UserTrackerImpl internal constructor(
     private val context: Context,
     private val userManager: UserManager,
     private val dumpManager: DumpManager,
@@ -70,13 +69,13 @@
     private val mutex = Any()
 
     override var userId: Int by SynchronizedDelegate(context.userId)
-        private set
+        protected set
 
     override var userHandle: UserHandle by SynchronizedDelegate(context.user)
-        private set
+        protected set
 
     override var userContext: Context by SynchronizedDelegate(context)
-        private set
+        protected set
 
     override val userContentResolver: ContentResolver
         get() = userContext.contentResolver
@@ -94,7 +93,7 @@
      * modified.
      */
     override var userProfiles: List<UserInfo> by SynchronizedDelegate(emptyList())
-        private set
+        protected set
 
     @GuardedBy("callbacks")
     private val callbacks: MutableList<DataItem> = ArrayList()
@@ -155,7 +154,7 @@
     }
 
     @WorkerThread
-    private fun handleSwitchUser(newUser: Int) {
+    protected open fun handleSwitchUser(newUser: Int) {
         Assert.isNotMainThread()
         if (newUser == UserHandle.USER_NULL) {
             Log.w(TAG, "handleSwitchUser - Couldn't get new id from intent")
@@ -174,7 +173,7 @@
     }
 
     @WorkerThread
-    private fun handleProfilesChanged() {
+    protected open fun handleProfilesChanged() {
         Assert.isNotMainThread()
 
         val profiles = userManager.getProfiles(userId)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index b39175e..3a624ca 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -33,7 +33,6 @@
 import static com.android.systemui.classifier.Classifier.QS_COLLAPSE;
 import static com.android.systemui.classifier.Classifier.QUICK_SETTINGS;
 import static com.android.systemui.classifier.Classifier.UNLOCK;
-import static com.android.systemui.shade.NotificationPanelView.DEBUG;
 import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_CLOSED;
 import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_OPEN;
 import static com.android.systemui.shade.ShadeExpansionStateManagerKt.STATE_OPENING;
@@ -41,9 +40,7 @@
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
 import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
 import static com.android.systemui.statusbar.StatusBarState.SHADE;
-import static com.android.systemui.statusbar.StatusBarState.SHADE_LOCKED;
 import static com.android.systemui.statusbar.VibratorHelper.TOUCH_VIBRATION_ATTRIBUTES;
-import static com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.ROWS_ALL;
 import static com.android.systemui.statusbar.notification.stack.StackStateAnimator.ANIMATION_DURATION_FOLD_TO_AOD;
 import static com.android.systemui.util.DumpUtilsKt.asIndenting;
 
@@ -53,10 +50,10 @@
 import android.animation.AnimatorListenerAdapter;
 import android.animation.ValueAnimator;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.Fragment;
 import android.app.StatusBarManager;
 import android.content.ContentResolver;
-import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.database.ContentObserver;
 import android.graphics.Canvas;
@@ -104,7 +101,6 @@
 import android.view.animation.Interpolator;
 import android.widget.FrameLayout;
 
-import androidx.annotation.Nullable;
 import androidx.constraintlayout.widget.ConstraintSet;
 
 import com.android.internal.annotations.VisibleForTesting;
@@ -135,7 +131,6 @@
 import com.android.systemui.camera.CameraGestureHelper;
 import com.android.systemui.classifier.Classifier;
 import com.android.systemui.classifier.FalsingCollector;
-import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.DisplayId;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.doze.DozeLog;
@@ -176,16 +171,13 @@
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.VibratorHelper;
-import com.android.systemui.statusbar.events.PrivacyDotViewController;
 import com.android.systemui.statusbar.notification.AnimatableProperty;
 import com.android.systemui.statusbar.notification.ConversationNotificationManager;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
 import com.android.systemui.statusbar.notification.PropertyAnimator;
 import com.android.systemui.statusbar.notification.ViewGroupFadeHelper;
-import com.android.systemui.statusbar.notification.collection.ListEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.render.ShadeViewManager;
 import com.android.systemui.statusbar.notification.row.ActivatableNotificationView;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.ExpandableView;
@@ -211,7 +203,6 @@
 import com.android.systemui.statusbar.phone.KeyguardStatusBarViewController;
 import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
 import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent;
-import com.android.systemui.statusbar.phone.NotificationIconAreaController;
 import com.android.systemui.statusbar.phone.PhoneStatusBarView;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.phone.ScrimController;
@@ -231,7 +222,6 @@
 import com.android.systemui.unfold.SysUIUnfoldComponent;
 import com.android.systemui.util.Compile;
 import com.android.systemui.util.LargeScreenUtils;
-import com.android.systemui.util.ListenerSet;
 import com.android.systemui.util.Utils;
 import com.android.systemui.util.time.SystemClock;
 import com.android.wm.shell.animation.FlingAnimationUtils;
@@ -259,28 +249,15 @@
     private static final boolean DEBUG_LOGCAT = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.DEBUG);
     private static final boolean SPEW_LOGCAT = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE);
     private static final boolean DEBUG_DRAWABLE = false;
-
     private static final VibrationEffect ADDITIONAL_TAP_REQUIRED_VIBRATION_EFFECT =
             VibrationEffect.get(VibrationEffect.EFFECT_STRENGTH_MEDIUM, false);
-
-    /**
-     * The parallax amount of the quick settings translation when dragging down the panel
-     */
+    /** The parallax amount of the quick settings translation when dragging down the panel. */
     private static final float QS_PARALLAX_AMOUNT = 0.175f;
-
-    /**
-     * Fling expanding QS.
-     */
+    /** Fling expanding QS. */
     public static final int FLING_EXPAND = 0;
-
-    /**
-     * Fling collapsing QS, potentially stopping when QS becomes QQS.
-     */
+    /** Fling collapsing QS, potentially stopping when QS becomes QQS. */
     private static final int FLING_COLLAPSE = 1;
-
-    /**
-     * Fling until QS is completely hidden.
-     */
+    /** Fling until QS is completely hidden. */
     private static final int FLING_HIDE = 2;
     private static final long ANIMATION_DELAY_ICON_FADE_IN =
             ActivityLaunchAnimator.TIMINGS.getTotalDuration()
@@ -294,6 +271,18 @@
      * when flinging. A low value will make it that most flings will reach the maximum overshoot.
      */
     private static final float FACTOR_OF_HIGH_VELOCITY_FOR_MAX_OVERSHOOT = 0.5f;
+    /**
+     * Maximum time before which we will expand the panel even for slow motions when getting a
+     * touch passed over from launcher.
+     */
+    private static final int MAX_TIME_TO_OPEN_WHEN_FLINGING_FROM_LAUNCHER = 300;
+    private static final int MAX_DOWN_EVENT_BUFFER_SIZE = 50;
+    private static final String COUNTER_PANEL_OPEN = "panel_open";
+    private static final String COUNTER_PANEL_OPEN_QS = "panel_open_qs";
+    private static final String COUNTER_PANEL_OPEN_PEEK = "panel_open_peek";
+    private static final Rect M_DUMMY_DIRTY_RECT = new Rect(0, 0, 1, 1);
+    private static final Rect EMPTY_RECT = new Rect();
+
     private final StatusBarTouchableRegionManager mStatusBarTouchableRegionManager;
     private final Resources mResources;
     private final KeyguardStateController mKeyguardStateController;
@@ -302,49 +291,24 @@
     private final LockscreenGestureLogger mLockscreenGestureLogger;
     private final SystemClock mSystemClock;
     private final ShadeLogger mShadeLog;
-
     private final DozeParameters mDozeParameters;
-    private final OnHeightChangedListener mOnHeightChangedListener = new OnHeightChangedListener();
-    private final Runnable mCollapseExpandAction = new CollapseExpandAction();
-    private final OnOverscrollTopChangedListener
-            mOnOverscrollTopChangedListener =
-            new OnOverscrollTopChangedListener();
-    private final OnEmptySpaceClickListener
-            mOnEmptySpaceClickListener =
-            new OnEmptySpaceClickListener();
-    private final MyOnHeadsUpChangedListener
-            mOnHeadsUpChangedListener =
-            new MyOnHeadsUpChangedListener();
-    private final HeightListener mHeightListener = new HeightListener();
+    private final Runnable mCollapseExpandAction = this::collapseOrExpand;
+    private final NsslOverscrollTopChangedListener mOnOverscrollTopChangedListener =
+            new NsslOverscrollTopChangedListener();
+    private final NotificationStackScrollLayout.OnEmptySpaceClickListener
+            mOnEmptySpaceClickListener = (x, y) -> onEmptySpaceClick();
+    private final ShadeHeadsUpChangedListener mOnHeadsUpChangedListener =
+            new ShadeHeadsUpChangedListener();
+    private final QS.HeightListener mHeightListener = this::onQsHeightChanged;
     private final ConfigurationListener mConfigurationListener = new ConfigurationListener();
     private final SettingsChangeObserver mSettingsChangeObserver;
-
-    @VisibleForTesting
-    final StatusBarStateListener mStatusBarStateListener =
-            new StatusBarStateListener();
+    private final StatusBarStateListener mStatusBarStateListener = new StatusBarStateListener();
     private final NotificationPanelView mView;
     private final VibratorHelper mVibratorHelper;
     private final MetricsLogger mMetricsLogger;
     private final ConfigurationController mConfigurationController;
     private final Provider<FlingAnimationUtils.Builder> mFlingAnimationUtilsBuilder;
     private final NotificationStackScrollLayoutController mNotificationStackScrollLayoutController;
-    private final NotificationIconAreaController mNotificationIconAreaController;
-
-    /**
-     * Maximum time before which we will expand the panel even for slow motions when getting a
-     * touch passed over from launcher.
-     */
-    private static final int MAX_TIME_TO_OPEN_WHEN_FLINGING_FROM_LAUNCHER = 300;
-
-    private static final int MAX_DOWN_EVENT_BUFFER_SIZE = 50;
-
-    private static final String COUNTER_PANEL_OPEN = "panel_open";
-    private static final String COUNTER_PANEL_OPEN_QS = "panel_open_qs";
-    private static final String COUNTER_PANEL_OPEN_PEEK = "panel_open_peek";
-
-    private static final Rect M_DUMMY_DIRTY_RECT = new Rect(0, 0, 1, 1);
-    private static final Rect EMPTY_RECT = new Rect();
-
     private final InteractionJankMonitor mInteractionJankMonitor;
     private final LayoutInflater mLayoutInflater;
     private final FeatureFlags mFeatureFlags;
@@ -364,15 +328,12 @@
     private final KeyguardStatusBarViewComponent.Factory mKeyguardStatusBarViewComponentFactory;
     private final FragmentService mFragmentService;
     private final ScrimController mScrimController;
-    private final PrivacyDotViewController mPrivacyDotViewController;
     private final NotificationRemoteInputManager mRemoteInputManager;
-
     private final LockscreenShadeTransitionController mLockscreenShadeTransitionController;
     private final ShadeTransitionController mShadeTransitionController;
     private final TapAgainViewController mTapAgainViewController;
     private final LargeScreenShadeHeaderController mLargeScreenShadeHeaderController;
     private final RecordingController mRecordingController;
-    private final PanelEventsEmitter mPanelEventsEmitter;
     private final boolean mVibrateOnOpening;
     private final VelocityTracker mVelocityTracker = VelocityTracker.obtain();
     private final FlingAnimationUtils mFlingAnimationUtilsClosing;
@@ -384,6 +345,11 @@
     private final Interpolator mBounceInterpolator;
     private final NotificationShadeWindowController mNotificationShadeWindowController;
     private final ShadeExpansionStateManager mShadeExpansionStateManager;
+    private final QS.ScrollListener mQsScrollListener = this::onQsPanelScrollChanged;
+    private final FalsingTapListener mFalsingTapListener = this::falsingAdditionalTapRequired;
+    private final FragmentListener mQsFragmentListener = new QsFragmentListener();
+    private final AccessibilityDelegate mAccessibilityDelegate = new ShadeAccessibilityDelegate();
+
     private long mDownTime;
     private boolean mTouchSlopExceededBeforeDown;
     private boolean mIsLaunchAnimationRunning;
@@ -405,13 +371,11 @@
     private float mKeyguardNotificationTopPadding;
     /** Current max allowed keyguard notifications determined by measuring the panel. */
     private int mMaxAllowedKeyguardNotifications;
-
     private KeyguardQsUserSwitchController mKeyguardQsUserSwitchController;
     private KeyguardUserSwitcherController mKeyguardUserSwitcherController;
     private KeyguardStatusBarView mKeyguardStatusBar;
     private KeyguardStatusBarViewController mKeyguardStatusBarViewController;
-    @VisibleForTesting
-    QS mQs;
+    private QS mQs;
     private FrameLayout mQsFrame;
     private final QsFrameTranslateController mQsFrameTranslateController;
     private KeyguardStatusViewController mKeyguardStatusViewController;
@@ -424,18 +388,11 @@
     private float mQuickQsHeaderHeight;
     private final ScreenOffAnimationController mScreenOffAnimationController;
     private final UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
-
     private int mQsTrackingPointer;
     private VelocityTracker mQsVelocityTracker;
     private boolean mQsTracking;
-
-    /**
-     * If set, the ongoing touch gesture might both trigger the expansion in {@link
-     * NotificationPanelView} and
-     * the expansion for quick settings.
-     */
+    /** Whether the ongoing gesture might both trigger the expansion in both the view and QS. */
     private boolean mConflictingQsExpansionGesture;
-
     private boolean mPanelExpanded;
 
     /**
@@ -490,11 +447,9 @@
      * Used for split shade, two finger gesture as well as accessibility shortcut to QS.
      * It needs to be set when movement starts as it resets at the end of expansion/collapse.
      */
-    @VisibleForTesting
-    boolean mQsExpandImmediate;
+    private boolean mQsExpandImmediate;
     private boolean mTwoFingerQsExpandPossible;
     private String mHeaderDebugInfo;
-
     /**
      * If we are in a panel collapsing motion, we reset scrollY of our scroll view but still
      * need to take this into account in our panel height calculation.
@@ -502,7 +457,6 @@
     private boolean mQsAnimatorExpand;
     private boolean mIsLaunchTransitionFinished;
     private ValueAnimator mQsSizeChangeAnimator;
-
     private boolean mQsScrimEnabled = true;
     private boolean mQsTouchAboveFalsingThreshold;
     private int mQsFalsingThreshold;
@@ -520,39 +474,27 @@
     private final FalsingManager mFalsingManager;
     private final FalsingCollector mFalsingCollector;
 
-    private final Runnable mHeadsUpExistenceChangedRunnable = () -> {
-        setHeadsUpAnimatingAway(false);
-        updatePanelExpansionAndVisibility();
-    };
     private boolean mShowIconsWhenExpanded;
     private int mIndicationBottomPadding;
     private int mAmbientIndicationBottomPadding;
+    /** Whether the notifications are displayed full width (no margins on the side). */
     private boolean mIsFullWidth;
     private boolean mBlockingExpansionForCurrentTouch;
+     // Following variables maintain state of events when input focus transfer may occur.
+    private boolean mExpectingSynthesizedDown;
+    private boolean mLastEventSynthesizedDown;
 
-    /**
-     * Following variables maintain state of events when input focus transfer may occur.
-     */
-    private boolean mExpectingSynthesizedDown; // expecting to see synthesized DOWN event
-    private boolean mLastEventSynthesizedDown; // last event was synthesized DOWN event
-
-    /**
-     * Current dark amount that follows regular interpolation curve of animation.
-     */
+    /** Current dark amount that follows regular interpolation curve of animation. */
     private float mInterpolatedDarkAmount;
-
     /**
      * Dark amount that animates from 0 to 1 or vice-versa in linear manner, even if the
      * interpolation curve is different.
      */
     private float mLinearDarkAmount;
-
     private boolean mPulsing;
     private boolean mHideIconsDuringLaunchAnimation = true;
     private int mStackScrollerMeasuringPass;
-    /**
-     * Non-null if there's a heads-up notification that we're currently tracking the position of.
-     */
+    /** Non-null if a heads-up notification's position is being tracked. */
     @Nullable
     private ExpandableNotificationRow mTrackedHeadsUpNotification;
     private final ArrayList<Consumer<ExpandableNotificationRow>>
@@ -582,17 +524,19 @@
     private final CommandQueue mCommandQueue;
     private final UserManager mUserManager;
     private final MediaDataManager mMediaDataManager;
+    @PanelState
+    private int mCurrentPanelState = STATE_CLOSED;
     private final SysUiState mSysUiState;
-
     private final NotificationShadeDepthController mDepthController;
     private final NavigationBarController mNavigationBarController;
     private final int mDisplayId;
 
-    private KeyguardIndicationController mKeyguardIndicationController;
+    private final KeyguardIndicationController mKeyguardIndicationController;
     private int mHeadsUpInset;
     private boolean mHeadsUpPinnedMode;
     private boolean mAllowExpandForSmallExpansion;
     private Runnable mExpandAfterLayoutRunnable;
+    private Runnable mHideExpandedRunnable;
 
     /**
      * The padding between the start of notifications and the qs boundary on the lockscreen.
@@ -600,94 +544,51 @@
      * qs boundary to be padded.
      */
     private int mLockscreenNotificationQSPadding;
-
     /**
      * The amount of progress we are currently in if we're transitioning to the full shade.
      * 0.0f means we're not transitioning yet, while 1 means we're all the way in the full
      * shade. This value can also go beyond 1.1 when we're overshooting!
      */
     private float mTransitioningToFullShadeProgress;
-
     /**
      * Position of the qs bottom during the full shade transition. This is needed as the toppadding
      * can change during state changes, which makes it much harder to do animations
      */
     private int mTransitionToFullShadeQSPosition;
-
-    /**
-     * Distance that the full shade transition takes in order for qs to fully transition to the
-     * shade.
-     */
+    /** Distance a full shade transition takes in order for qs to fully transition to the shade. */
     private int mDistanceForQSFullShadeTransition;
-
-    /**
-     * The translation amount for QS for the full shade transition
-     */
+    /** The translation amount for QS for the full shade transition. */
     private float mQsTranslationForFullShadeTransition;
 
-    /**
-     * The maximum overshoot allowed for the top padding for the full shade transition
-     */
+    /** The maximum overshoot allowed for the top padding for the full shade transition. */
     private int mMaxOverscrollAmountForPulse;
-
-    /**
-     * Should we animate the next bounds update
-     */
+    /** Should we animate the next bounds update. */
     private boolean mAnimateNextNotificationBounds;
-    /**
-     * The delay for the next bounds animation
-     */
+    /** The delay for the next bounds animation. */
     private long mNotificationBoundsAnimationDelay;
-
-    /**
-     * The duration of the notification bounds animation
-     */
+    /** The duration of the notification bounds animation. */
     private long mNotificationBoundsAnimationDuration;
 
-    /**
-     * Is this a collapse that started on the panel where we should allow the panel to intercept
-     */
+    /** Whether a collapse that started on the panel should allow the panel to intercept. */
     private boolean mIsPanelCollapseOnQQS;
-
     private boolean mAnimatingQS;
-
-    /**
-     * The end bounds of a clipping animation.
-     */
+    /** The end bounds of a clipping animation. */
     private final Rect mQsClippingAnimationEndBounds = new Rect();
-
-    /**
-     * The animator for the qs clipping bounds.
-     */
+    /** The animator for the qs clipping bounds. */
     private ValueAnimator mQsClippingAnimation = null;
-
-    /**
-     * Is the current animator resetting the qs translation.
-     */
+    /** Whether the current animator is resetting the qs translation. */
     private boolean mIsQsTranslationResetAnimator;
 
-    /**
-     * Is the current animator resetting the pulse expansion after a drag down
-     */
+    /** Whether the current animator is resetting the pulse expansion after a drag down. */
     private boolean mIsPulseExpansionResetAnimator;
     private final Rect mKeyguardStatusAreaClipBounds = new Rect();
     private final Region mQsInterceptRegion = new Region();
-
-    /**
-     * The alpha of the views which only show on the keyguard but not in shade / shade locked
-     */
+    /** Alpha of the views which only show on the keyguard but not in shade / shade locked. */
     private float mKeyguardOnlyContentAlpha = 1.0f;
-
-    /**
-     * The translationY of the views which only show on the keyguard but in shade / shade locked.
-     */
+    /** Y translation of the views that only show on the keyguard but in shade / shade locked. */
     private int mKeyguardOnlyTransitionTranslationY = 0;
-
     private float mUdfpsMaxYBurnInOffset;
-
-    /**
-     * Are we currently in gesture navigation
-     */
+    /** Are we currently in gesture navigation. */
     private boolean mIsGestureNavigation;
     private int mOldLayoutDirection;
     private NotificationShelfController mNotificationShelfController;
@@ -700,6 +601,7 @@
     private int mQsClipTop;
     private int mQsClipBottom;
     private boolean mQsVisible;
+
     private final ContentResolver mContentResolver;
     private float mMinFraction;
 
@@ -718,55 +620,7 @@
 
     private final NotificationListContainer mNotificationListContainer;
     private final NotificationStackSizeCalculator mNotificationStackSizeCalculator;
-
     private final NPVCDownEventState.Buffer mLastDownEvents;
-
-    private final Runnable mAnimateKeyguardBottomAreaInvisibleEndRunnable =
-            () -> mKeyguardBottomArea.setVisibility(View.GONE);
-
-    private final AccessibilityDelegate mAccessibilityDelegate = new AccessibilityDelegate() {
-        @Override
-        public void onInitializeAccessibilityNodeInfo(View host,
-                AccessibilityNodeInfo info) {
-            super.onInitializeAccessibilityNodeInfo(host, info);
-            info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD);
-            info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP);
-        }
-
-        @Override
-        public boolean performAccessibilityAction(View host, int action, Bundle args) {
-            if (action
-                    == AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD.getId()
-                    || action
-                    == AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP.getId()) {
-                mStatusBarKeyguardViewManager.showBouncer(true);
-                return true;
-            }
-            return super.performAccessibilityAction(host, action, args);
-        }
-    };
-
-    private final FalsingTapListener mFalsingTapListener = new FalsingTapListener() {
-        @Override
-        public void onAdditionalTapRequired() {
-            if (mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED) {
-                mTapAgainViewController.show();
-            } else {
-                mKeyguardIndicationController.showTransientIndication(
-                        R.string.notification_tap_again);
-            }
-
-            if (!mStatusBarStateController.isDozing()) {
-                mVibratorHelper.vibrate(
-                        Process.myUid(),
-                        mView.getContext().getPackageName(),
-                        ADDITIONAL_TAP_REQUIRED_VIBRATION_EFFECT,
-                        "falsing-additional-tap-required",
-                        TOUCH_VIBRATION_ATTRIBUTES);
-            }
-        }
-    };
-
     private final CameraGestureHelper mCameraGestureHelper;
     private final KeyguardBottomAreaViewModel mKeyguardBottomAreaViewModel;
     private final KeyguardBottomAreaInteractor mKeyguardBottomAreaInteractor;
@@ -816,8 +670,20 @@
     private boolean mGestureWaitForTouchSlop;
     private boolean mIgnoreXTouchSlop;
     private boolean mExpandLatencyTracking;
+
     private final Runnable mFlingCollapseRunnable = () -> fling(0, false /* expand */,
             mNextCollapseSpeedUpFactor, false /* expandBecauseOfFalsing */);
+    private final Runnable mAnimateKeyguardBottomAreaInvisibleEndRunnable =
+            () -> mKeyguardBottomArea.setVisibility(View.GONE);
+    private final Runnable mHeadsUpExistenceChangedRunnable = () -> {
+        setHeadsUpAnimatingAway(false);
+        updatePanelExpansionAndVisibility();
+    };
+    private final Runnable mMaybeHideExpandedRunnable = () -> {
+        if (getExpansionFraction() == 0.0f) {
+            getView().post(mHideExpandedRunnable);
+        }
+    };
 
     @Inject
     public NotificationPanelViewController(NotificationPanelView view,
@@ -852,7 +718,6 @@
             KeyguardUserSwitcherComponent.Factory keyguardUserSwitcherComponentFactory,
             KeyguardStatusBarViewComponent.Factory keyguardStatusBarViewComponentFactory,
             LockscreenShadeTransitionController lockscreenShadeTransitionController,
-            NotificationIconAreaController notificationIconAreaController,
             AuthController authController,
             ScrimController scrimController,
             UserManager userManager,
@@ -861,7 +726,6 @@
             AmbientState ambientState,
             LockIconViewController lockIconViewController,
             KeyguardMediaController keyguardMediaController,
-            PrivacyDotViewController privacyDotViewController,
             TapAgainViewController tapAgainViewController,
             NavigationModeController navigationModeController,
             NavigationBarController navigationBarController,
@@ -879,8 +743,8 @@
             SysUiState sysUiState,
             Provider<KeyguardBottomAreaViewController> keyguardBottomAreaViewControllerProvider,
             KeyguardUnlockAnimationController keyguardUnlockAnimationController,
+            KeyguardIndicationController keyguardIndicationController,
             NotificationListContainer notificationListContainer,
-            PanelEventsEmitter panelEventsEmitter,
             NotificationStackSizeCalculator notificationStackSizeCalculator,
             UnlockedScreenOffAnimationController unlockedScreenOffAnimationController,
             ShadeTransitionController shadeTransitionController,
@@ -900,7 +764,6 @@
         mLockscreenGestureLogger = lockscreenGestureLogger;
         mShadeExpansionStateManager = shadeExpansionStateManager;
         mShadeLog = shadeLogger;
-        TouchHandler touchHandler = createTouchHandler();
         mView.addOnAttachStateChangeListener(new View.OnAttachStateChangeListener() {
             @Override
             public void onViewAttachedToWindow(View v) {
@@ -908,16 +771,16 @@
             }
 
             @Override
-            public void onViewDetachedFromWindow(View v) {
-            }
+            public void onViewDetachedFromWindow(View v) {}
         });
 
-        mView.addOnLayoutChangeListener(createLayoutChangeListener());
-        mView.setOnTouchListener(touchHandler);
-        mView.setOnConfigurationChangedListener(createOnConfigurationChangedListener());
+        mView.addOnLayoutChangeListener(new ShadeLayoutChangeListener());
+        mView.setOnTouchListener(createTouchHandler());
+        mView.setOnConfigurationChangedListener(config -> loadDimens());
 
         mResources = mView.getResources();
         mKeyguardStateController = keyguardStateController;
+        mKeyguardIndicationController = keyguardIndicationController;
         mStatusBarStateController = (SysuiStatusBarStateController) statusBarStateController;
         mNotificationShadeWindowController = notificationShadeWindowController;
         FlingAnimationUtils.Builder fauBuilder = flingAnimationUtilsBuilder.get();
@@ -950,7 +813,6 @@
         mInteractionJankMonitor = interactionJankMonitor;
         mSystemClock = systemClock;
         mKeyguardMediaController = keyguardMediaController;
-        mPrivacyDotViewController = privacyDotViewController;
         mMetricsLogger = metricsLogger;
         mConfigurationController = configurationController;
         mFlingAnimationUtilsBuilder = flingAnimationUtilsBuilder;
@@ -962,7 +824,6 @@
         mKeyguardBottomAreaViewControllerProvider = keyguardBottomAreaViewControllerProvider;
         mNotificationsQSContainerController.init();
         mNotificationStackScrollLayoutController = notificationStackScrollLayoutController;
-        mNotificationIconAreaController = notificationIconAreaController;
         mKeyguardStatusViewComponentFactory = keyguardStatusViewComponentFactory;
         mKeyguardStatusBarViewComponentFactory = keyguardStatusBarViewComponentFactory;
         mDepthController = notificationShadeDepthController;
@@ -993,7 +854,6 @@
         mMediaDataManager = mediaDataManager;
         mTapAgainViewController = tapAgainViewController;
         mSysUiState = sysUiState;
-        mPanelEventsEmitter = panelEventsEmitter;
         pulseExpansionHandler.setPulseExpandAbortListener(() -> {
             if (mQs != null) {
                 mQs.animateHeaderSlidingOut();
@@ -1006,10 +866,7 @@
         mShadeTransitionController = shadeTransitionController;
         lockscreenShadeTransitionController.setNotificationPanelController(this);
         shadeTransitionController.setNotificationPanelViewController(this);
-        DynamicPrivacyControlListener
-                dynamicPrivacyControlListener =
-                new DynamicPrivacyControlListener();
-        dynamicPrivacyController.addListener(dynamicPrivacyControlListener);
+        dynamicPrivacyController.addListener(this::onDynamicPrivacyChanged);
 
         shadeExpansionStateManager.addStateListener(this::onPanelStateChanged);
 
@@ -1033,13 +890,14 @@
         mIsGestureNavigation = QuickStepContract.isGesturalMode(currentMode);
 
         mView.setBackgroundColor(Color.TRANSPARENT);
-        OnAttachStateChangeListener onAttachStateChangeListener = new OnAttachStateChangeListener();
+        ShadeAttachStateChangeListener
+                onAttachStateChangeListener = new ShadeAttachStateChangeListener();
         mView.addOnAttachStateChangeListener(onAttachStateChangeListener);
         if (mView.isAttachedToWindow()) {
             onAttachStateChangeListener.onViewAttachedToWindow(mView);
         }
 
-        mView.setOnApplyWindowInsetsListener(new OnApplyWindowInsetsListener());
+        mView.setOnApplyWindowInsetsListener((v, insets) -> onApplyShadeWindowInsets(insets));
 
         if (DEBUG_DRAWABLE) {
             mView.getOverlay().add(new DebugDrawable());
@@ -1058,57 +916,68 @@
                 new KeyguardUnlockAnimationController.KeyguardUnlockAnimationListener() {
                     @Override
                     public void onUnlockAnimationFinished() {
-                        // Make sure the clock is in the correct position after the unlock animation
-                        // so that it's not in the wrong place when we show the keyguard again.
-                        positionClockAndNotifications(true /* forceClockUpdate */);
+                        unlockAnimationFinished();
                     }
 
                     @Override
                     public void onUnlockAnimationStarted(
                             boolean playingCannedAnimation,
                             boolean isWakeAndUnlock,
-                            long unlockAnimationStartDelay,
+                            long startDelay,
                             long unlockAnimationDuration) {
-                        // Disable blurs while we're unlocking so that panel expansion does not
-                        // cause blurring. This will eventually be re-enabled by the panel view on
-                        // ACTION_UP, since the user's finger might still be down after a swipe to
-                        // unlock gesture, and we don't want that to cause blurring either.
-                        mDepthController.setBlursDisabledForUnlock(mTracking);
-
-                        if (playingCannedAnimation && !isWakeAndUnlock) {
-                            // Hide the panel so it's not in the way or the surface behind the
-                            // keyguard, which will be appearing. If we're wake and unlocking, the
-                            // lock screen is hidden instantly so should not be flung away.
-                            if (isTracking() || isFlinging()) {
-                                // Instant collpase the notification panel since the notification
-                                // panel is already in the middle animating
-                                onTrackingStopped(false);
-                                instantCollapse();
-                            } else {
-                                mView.animate()
-                                        .alpha(0f)
-                                        .setStartDelay(0)
-                                        // Translate up by 4%.
-                                        .translationY(mView.getHeight() * -0.04f)
-                                        // This start delay is to give us time to animate out before
-                                        // the launcher icons animation starts, so use that as our
-                                        // duration.
-                                        .setDuration(unlockAnimationStartDelay)
-                                        .setInterpolator(EMPHASIZED_ACCELERATE)
-                                        .withEndAction(() -> {
-                                            instantCollapse();
-                                            mView.setAlpha(1f);
-                                            mView.setTranslationY(0f);
-                                        })
-                                        .start();
-                            }
-                        }
+                        unlockAnimationStarted(playingCannedAnimation, isWakeAndUnlock, startDelay);
                     }
                 });
         mCameraGestureHelper = cameraGestureHelper;
         mKeyguardBottomAreaInteractor = keyguardBottomAreaInteractor;
     }
 
+    private void unlockAnimationFinished() {
+        // Make sure the clock is in the correct position after the unlock animation
+        // so that it's not in the wrong place when we show the keyguard again.
+        positionClockAndNotifications(true /* forceClockUpdate */);
+    }
+
+    private void unlockAnimationStarted(
+            boolean playingCannedAnimation,
+            boolean isWakeAndUnlock,
+            long unlockAnimationStartDelay) {
+        // Disable blurs while we're unlocking so that panel expansion does not
+        // cause blurring. This will eventually be re-enabled by the panel view on
+        // ACTION_UP, since the user's finger might still be down after a swipe to
+        // unlock gesture, and we don't want that to cause blurring either.
+        mDepthController.setBlursDisabledForUnlock(mTracking);
+
+        if (playingCannedAnimation && !isWakeAndUnlock) {
+            // Hide the panel so it's not in the way or the surface behind the
+            // keyguard, which will be appearing. If we're wake and unlocking, the
+            // lock screen is hidden instantly so should not be flung away.
+            if (isTracking() || mIsFlinging) {
+                // Instant collapse the notification panel since the notification
+                // panel is already in the middle animating
+                onTrackingStopped(false);
+                instantCollapse();
+            } else {
+                mView.animate()
+                        .alpha(0f)
+                        .setStartDelay(0)
+                        // Translate up by 4%.
+                        .translationY(mView.getHeight() * -0.04f)
+                        // This start delay is to give us time to animate out before
+                        // the launcher icons animation starts, so use that as our
+                        // duration.
+                        .setDuration(unlockAnimationStartDelay)
+                        .setInterpolator(EMPHASIZED_ACCELERATE)
+                        .withEndAction(() -> {
+                            instantCollapse();
+                            mView.setAlpha(1f);
+                            mView.setTranslationY(0f);
+                        })
+                        .start();
+            }
+        }
+    }
+
     @VisibleForTesting
     void onFinishInflate() {
         loadDimens();
@@ -1145,7 +1014,7 @@
                 R.id.notification_stack_scroller);
         mNotificationStackScrollLayoutController.attach(stackScrollLayout);
         mNotificationStackScrollLayoutController.setOnHeightChangedListener(
-                mOnHeightChangedListener);
+                new NsslHeightChangedListener());
         mNotificationStackScrollLayoutController.setOverscrollTopChangedListener(
                 mOnOverscrollTopChangedListener);
         mNotificationStackScrollLayoutController.setOnScrollListener(this::onNotificationScrolled);
@@ -1153,7 +1022,7 @@
         mNotificationStackScrollLayoutController.setOnEmptySpaceClickListener(
                 mOnEmptySpaceClickListener);
         addTrackingHeadsUpListener(mNotificationStackScrollLayoutController::setTrackingHeadsUp);
-        mKeyguardBottomArea = mView.findViewById(R.id.keyguard_bottom_area);
+        setKeyguardBottomArea(mView.findViewById(R.id.keyguard_bottom_area));
 
         initBottomArea();
 
@@ -1266,11 +1135,6 @@
         }
     }
 
-    private void setCentralSurfaces(CentralSurfaces centralSurfaces) {
-        // TODO: this can be injected.
-        mCentralSurfaces = centralSurfaces;
-    }
-
     public void updateResources() {
         mSplitShadeNotificationsScrimMarginBottom =
                 mResources.getDimensionPixelSize(
@@ -1356,7 +1220,7 @@
 
     @VisibleForTesting
     void reInflateViews() {
-        if (DEBUG_LOGCAT) Log.d(TAG, "reInflateViews");
+        debugLog("reInflateViews");
         // Re-inflate the status view group.
         KeyguardStatusView keyguardStatusView =
                 mNotificationContainerParent.findViewById(R.id.keyguard_status_view);
@@ -1402,7 +1266,7 @@
         int index = mView.indexOfChild(mKeyguardBottomArea);
         mView.removeView(mKeyguardBottomArea);
         KeyguardBottomAreaView oldBottomArea = mKeyguardBottomArea;
-        mKeyguardBottomArea = mKeyguardBottomAreaViewControllerProvider.get().getView();
+        setKeyguardBottomArea(mKeyguardBottomAreaViewControllerProvider.get().getView());
         mKeyguardBottomArea.initFrom(oldBottomArea);
         mView.addView(mKeyguardBottomArea, index);
         initBottomArea();
@@ -1435,6 +1299,11 @@
         mNotificationPanelUnfoldAnimationController.ifPresent(u -> u.setup(mView));
     }
 
+    @VisibleForTesting
+    void setQs(QS qs) {
+        mQs = qs;
+    }
+
     private void attachSplitShadeMediaPlayerContainer(FrameLayout container) {
         mKeyguardMediaController.attachSplitShadeContainer(container);
     }
@@ -1449,12 +1318,7 @@
     }
 
     @VisibleForTesting
-    boolean getClosing() {
-        return mClosing;
-    }
-
-    @VisibleForTesting
-    boolean getIsFlinging() {
+    boolean isFlinging() {
         return mIsFlinging;
     }
 
@@ -1481,8 +1345,8 @@
         return mHintAnimationRunning || mUnlockedScreenOffAnimationController.isAnimationPlaying();
     }
 
-    public void setKeyguardIndicationController(KeyguardIndicationController indicationController) {
-        mKeyguardIndicationController = indicationController;
+    private void setKeyguardBottomArea(KeyguardBottomAreaView keyguardBottomArea) {
+        mKeyguardBottomArea = keyguardBottomArea;
         mKeyguardIndicationController.setIndicationArea(mKeyguardBottomArea);
     }
 
@@ -1929,13 +1793,13 @@
             setQsExpandImmediate(true);
             setShowShelfOnly(true);
         }
-        if (DEBUG) this.logf("collapse: " + this);
+        debugLog("collapse: %s", this);
         if (canPanelBeCollapsed()) {
             cancelHeightAnimator();
             notifyExpandingStarted();
 
             // Set after notifyExpandingStarted, as notifyExpandingStarted resets the closing state.
-            setIsClosing(true);
+            setClosing(true);
             if (delayed) {
                 mNextCollapseSpeedUpFactor = speedUpFactor;
                 this.mView.postDelayed(mFlingCollapseRunnable, 120);
@@ -1945,13 +1809,19 @@
         }
     }
 
-    private void setQsExpandImmediate(boolean expandImmediate) {
+    @VisibleForTesting
+    void setQsExpandImmediate(boolean expandImmediate) {
         if (expandImmediate != mQsExpandImmediate) {
             mQsExpandImmediate = expandImmediate;
-            mPanelEventsEmitter.notifyExpandImmediateChange(expandImmediate);
+            mShadeExpansionStateManager.notifyExpandImmediateChange(expandImmediate);
         }
     }
 
+    @VisibleForTesting
+    boolean isQsExpandImmediate() {
+        return mQsExpandImmediate;
+    }
+
     private void setShowShelfOnly(boolean shelfOnly) {
         mNotificationStackScrollLayoutController.setShouldShowShelfOnly(
                 shelfOnly && !mSplitShadeEnabled);
@@ -1959,7 +1829,7 @@
 
     public void closeQs() {
         cancelQsAnimation();
-        setQsExpansion(mQsMinExpansionHeight);
+        setQsExpansionHeight(mQsMinExpansionHeight);
     }
 
     @VisibleForTesting
@@ -1997,7 +1867,7 @@
             }
             float height = mQsExpansionHeight;
             mQsExpansionAnimator.cancel();
-            setQsExpansion(height);
+            setQsExpansionHeight(height);
         }
         flingSettings(0 /* vel */, animateAway ? FLING_HIDE : FLING_COLLAPSE);
     }
@@ -2021,7 +1891,7 @@
             // case but currently motion in portrait looks worse than when using flingSettings.
             // TODO: make below function transitioning smoothly also in portrait with null target
             mLockscreenShadeTransitionController.goToLockedShade(
-                    /* expandedView= */null, /* needsQSAnimation= */false);
+                    /* expandedView= */null, /* needsQSAnimation= */true);
         } else if (isFullyCollapsed()) {
             expand(true /* animate */);
         } else {
@@ -2038,12 +1908,12 @@
         }
     }
 
-    public void fling(float vel, boolean expand) {
+    private void fling(float vel) {
         GestureRecorder gr = mCentralSurfaces.getGestureRecorder();
         if (gr != null) {
             gr.tag("fling " + ((vel > 0) ? "open" : "closed"), "notifications,v=" + vel);
         }
-        fling(vel, expand, 1.0f /* collapseSpeedUpFactor */, false);
+        fling(vel, true, 1.0f /* collapseSpeedUpFactor */, false);
     }
 
     @VisibleForTesting
@@ -2130,7 +2000,7 @@
             @Override
             public void onAnimationEnd(Animator animation) {
                 if (shouldSpringBack && !mCancelled) {
-                    // After the shade is flinged open to an overscrolled state, spring back
+                    // After the shade is flung open to an overscrolled state, spring back
                     // the shade by reducing section padding to 0.
                     springBack();
                 } else {
@@ -2160,7 +2030,7 @@
     }
 
     private boolean onQsIntercept(MotionEvent event) {
-        if (DEBUG_LOGCAT) Log.d(TAG, "onQsIntercept");
+        debugLog("onQsIntercept");
         int pointerIndex = event.findPointerIndex(mQsTrackingPointer);
         if (pointerIndex < 0) {
             pointerIndex = 0;
@@ -2210,7 +2080,7 @@
                     // Already tracking because onOverscrolled was called. We need to update here
                     // so we don't stop for a frame until the next touch event gets handled in
                     // onTouchEvent.
-                    setQsExpansion(h + mInitialHeightOnTouch);
+                    setQsExpansionHeight(h + mInitialHeightOnTouch);
                     trackMovement(event);
                     return true;
                 } else {
@@ -2221,7 +2091,7 @@
                 if ((h > touchSlop || (h < -touchSlop && mQsExpanded))
                         && Math.abs(h) > Math.abs(x - mInitialTouchX)
                         && shouldQuickSettingsIntercept(mInitialTouchX, mInitialTouchY, h)) {
-                    if (DEBUG_LOGCAT) Log.d(TAG, "onQsIntercept - start tracking expansion");
+                    debugLog("onQsIntercept - start tracking expansion");
                     mView.getParent().requestDisallowInterceptTouchEvent(true);
                     mShadeLog.onQsInterceptMoveQsTrackingEnabled(h);
                     mQsTracking = true;
@@ -2280,7 +2150,7 @@
     private void initDownStates(MotionEvent event) {
         if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
             mQsTouchAboveFalsingThreshold = mQsFullyExpanded;
-            mDozingOnDown = isDozing();
+            mDozingOnDown = mDozing;
             mDownX = event.getX();
             mDownY = event.getY();
             mCollapsedOnDown = isFullyCollapsed();
@@ -2330,7 +2200,7 @@
         float vel = getCurrentQSVelocity();
         boolean expandsQs = flingExpandsQs(vel);
         if (expandsQs) {
-            if (mFalsingManager.isUnlockingDisabled() || isFalseTouch(QUICK_SETTINGS)) {
+            if (mFalsingManager.isUnlockingDisabled() || isFalseTouch()) {
                 expandsQs = false;
             } else {
                 logQsSwipeDown(y);
@@ -2369,9 +2239,9 @@
         }
     }
 
-    private boolean isFalseTouch(@Classifier.InteractionType int interactionType) {
+    private boolean isFalseTouch() {
         if (mFalsingManager.isClassifierEnabled()) {
-            return mFalsingManager.isFalseTouch(interactionType);
+            return mFalsingManager.isFalseTouch(Classifier.QUICK_SETTINGS);
         }
         return !mQsTouchAboveFalsingThreshold;
     }
@@ -2497,7 +2367,7 @@
     private void handleQsDown(MotionEvent event) {
         if (event.getActionMasked() == MotionEvent.ACTION_DOWN && shouldQuickSettingsIntercept(
                 event.getX(), event.getY(), -1)) {
-            if (DEBUG_LOGCAT) Log.d(TAG, "handleQsDown");
+            debugLog("handleQsDown");
             mFalsingCollector.onQsDown();
             mShadeLog.logMotionEvent(event, "handleQsDown: down action, QS tracking enabled");
             mQsTracking = true;
@@ -2511,9 +2381,7 @@
         }
     }
 
-    /**
-     * Input focus transfer is about to happen.
-     */
+    /** Input focus transfer is about to happen. */
     public void startWaitingForOpenPanelGesture() {
         if (!isFullyCollapsed()) {
             return;
@@ -2545,7 +2413,7 @@
             } else {
                 // Window never will receive touch events that typically trigger haptic on open.
                 maybeVibrateOnOpening(false /* openingWithTouch */);
-                fling(velocity > 1f ? 1000f * velocity : 0, true /* expand */);
+                fling(velocity > 1f ? 1000f * velocity : 0  /* expand */);
             }
             onTrackingStopped(false);
         }
@@ -2619,9 +2487,9 @@
                 break;
 
             case MotionEvent.ACTION_MOVE:
-                if (DEBUG_LOGCAT) Log.d(TAG, "onQSTouch move");
+                debugLog("onQSTouch move");
                 mShadeLog.logMotionEvent(event, "onQsTouch: move action, setting QS expansion");
-                setQsExpansion(h + mInitialHeightOnTouch);
+                setQsExpansionHeight(h + mInitialHeightOnTouch);
                 if (h >= getFalsingThreshold()) {
                     mQsTouchAboveFalsingThreshold = true;
                 }
@@ -2668,14 +2536,14 @@
 
         // Reset scroll position and apply that position to the expanded height.
         float height = mQsExpansionHeight;
-        setQsExpansion(height);
+        setQsExpansionHeight(height);
         updateExpandedHeightToMaxHeight();
         mNotificationStackScrollLayoutController.checkSnoozeLeavebehind();
 
         // When expanding QS, let's authenticate the user if possible,
         // this will speed up notification actions.
-        if (height == 0) {
-            mCentralSurfaces.requestFaceAuth(false, FaceAuthApiRequestReason.QS_EXPANDED);
+        if (height == 0 && !mKeyguardStateController.canDismissLockScreen()) {
+            mUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.QS_EXPANDED);
         }
     }
 
@@ -2696,6 +2564,9 @@
                 navigationBarView.onStatusBarPanelStateChanged();
             }
             mShadeExpansionStateManager.onQsExpansionChanged(expanded);
+            mShadeLog.logQsExpansionChanged("QS Expansion Changed.", expanded,
+                    mQsMinExpansionHeight, mQsMaxExpansionHeight, mStackScrollerOverscrolling,
+                    mDozing, mQsAnimatorExpand, mAnimatingQS);
         }
     }
 
@@ -2740,7 +2611,7 @@
         mQs.setExpanded(mQsExpanded);
     }
 
-    void setQsExpansion(float height) {
+    void setQsExpansionHeight(float height) {
         height = Math.min(Math.max(height, mQsMinExpansionHeight), mQsMaxExpansionHeight);
         mQsFullyExpanded = height == mQsMaxExpansionHeight && mQsMaxExpansionHeight != 0;
         boolean qsAnimatingAway = !mQsAnimatorExpand && mAnimatingQS;
@@ -2907,7 +2778,7 @@
     }
 
     private int calculateLeftQsClippingBound() {
-        if (isFullWidth()) {
+        if (mIsFullWidth) {
             // left bounds can ignore insets, it should always reach the edge of the screen
             return 0;
         } else {
@@ -2916,7 +2787,7 @@
     }
 
     private int calculateRightQsClippingBound() {
-        if (isFullWidth()) {
+        if (mIsFullWidth) {
             return getView().getRight() + mDisplayRightInset;
         } else {
             return mNotificationStackScrollLayoutController.getRight();
@@ -2984,7 +2855,7 @@
         // Fancy clipping for quick settings
         int radius = mScrimCornerRadius;
         boolean clipStatusView = false;
-        if (isFullWidth()) {
+        if (mIsFullWidth) {
             // The padding on this area is large enough that we can use a cheaper clipping strategy
             mKeyguardStatusAreaClipBounds.set(left, top, right, bottom);
             clipStatusView = qsVisible;
@@ -3047,11 +2918,23 @@
         // relative to NotificationStackScrollLayout
         int nsslLeft = left - mNotificationStackScrollLayoutController.getLeft();
         int nsslRight = right - mNotificationStackScrollLayoutController.getLeft();
-        int nsslTop = top - mNotificationStackScrollLayoutController.getTop();
+        int nsslTop = getNotificationsClippingTopBounds(top);
         int nsslBottom = bottom - mNotificationStackScrollLayoutController.getTop();
         int bottomRadius = mSplitShadeEnabled ? radius : 0;
+        int topRadius = mSplitShadeEnabled && mExpandingFromHeadsUp ? 0 : radius;
         mNotificationStackScrollLayoutController.setRoundedClippingBounds(
-                nsslLeft, nsslTop, nsslRight, nsslBottom, radius, bottomRadius);
+                nsslLeft, nsslTop, nsslRight, nsslBottom, topRadius, bottomRadius);
+    }
+
+    private int getNotificationsClippingTopBounds(int qsTop) {
+        if (mSplitShadeEnabled && mExpandingFromHeadsUp) {
+            // in split shade nssl has extra top margin so clipping at top 0 is not enough, we need
+            // to set top clipping bound to negative value to allow HUN to go up to the top edge of
+            // the screen without clipping.
+            return -mAmbientState.getStackTopMargin();
+        } else {
+            return qsTop - mNotificationStackScrollLayoutController.getTop();
+        }
     }
 
     private float getQSEdgePosition() {
@@ -3122,10 +3005,7 @@
         }
     }
 
-    /**
-     * @return the topPadding of notifications when on keyguard not respecting quick settings
-     * expansion
-     */
+    /** Returns the topPadding of notifications when on keyguard not respecting QS expansion. */
     private int getKeyguardNotificationStaticPadding() {
         if (!mKeyguardShowing) {
             return 0;
@@ -3157,17 +3037,18 @@
      * shade. 0.0f means we're not transitioning yet.
      */
     public void setTransitionToFullShadeAmount(float pxAmount, boolean animate, long delay) {
-        if (animate && isFullWidth()) {
+        if (animate && mIsFullWidth) {
             animateNextNotificationBounds(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE,
                     delay);
             mIsQsTranslationResetAnimator = mQsTranslationForFullShadeTransition > 0.0f;
         }
-
-        if (mSplitShadeEnabled) {
-            updateQsExpansionForLockscreenToShadeTransition(pxAmount);
-        }
         float endPosition = 0;
         if (pxAmount > 0.0f) {
+            if (mSplitShadeEnabled) {
+                float qsHeight = MathUtils.lerp(mQsMinExpansionHeight, mQsMaxExpansionHeight,
+                        mLockscreenShadeTransitionController.getQSDragProgress());
+                setQsExpansionHeight(qsHeight);
+            }
             if (mNotificationStackScrollLayoutController.getVisibleNotificationCount() == 0
                     && !mMediaDataManager.hasActiveMediaOrRecommendation()) {
                 // No notifications are visible, let's animate to the height of qs instead
@@ -3205,22 +3086,7 @@
         updateQsExpansion();
     }
 
-    private void updateQsExpansionForLockscreenToShadeTransition(float pxAmount) {
-        float qsExpansion = 0;
-        if (pxAmount > 0.0f) {
-            qsExpansion = MathUtils.lerp(mQsMinExpansionHeight, mQsMaxExpansionHeight,
-                    mLockscreenShadeTransitionController.getQSDragProgress());
-        }
-        // SHADE_LOCKED means transition is over and we don't want further updates
-        if (mBarState != SHADE_LOCKED) {
-            setQsExpansion(qsExpansion);
-        }
-    }
-
-    /**
-     * Notify the panel that the pulse expansion has finished and that we're going to the full
-     * shade
-     */
+    /** Called when pulse expansion has finished and this is going to the full shade. */
     public void onPulseExpansionFinished() {
         animateNextNotificationBounds(StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE, 0);
         mIsPulseExpansionResetAnimator = true;
@@ -3275,9 +3141,7 @@
         }
     }
 
-    /**
-     * @see #flingSettings(float, int, Runnable, boolean)
-     */
+    /** @see #flingSettings(float, int, Runnable, boolean) */
     public void flingSettings(float vel, int type) {
         flingSettings(vel, type, null /* onFinishRunnable */, false /* isClick */);
     }
@@ -3334,7 +3198,7 @@
             animator.setDuration(350);
         }
         animator.addUpdateListener(
-                animation -> setQsExpansion((Float) animation.getAnimatedValue()));
+                animation -> setQsExpansionHeight((Float) animation.getAnimatedValue()));
         animator.addListener(new AnimatorListenerAdapter() {
             private boolean mIsCanceled;
 
@@ -3410,7 +3274,8 @@
         return !mSplitShadeEnabled && (isInSettings() || mIsPanelCollapseOnQQS);
     }
 
-    public int getMaxPanelHeight() {
+    @VisibleForTesting
+    int getMaxPanelHeight() {
         int min = mStatusBarMinHeight;
         if (!(mBarState == KEYGUARD)
                 && mNotificationStackScrollLayoutController.getNotGoneChildCount() == 0) {
@@ -3444,19 +3309,35 @@
     }
 
     private void onHeightUpdated(float expandedHeight) {
+        if (expandedHeight <= 0) {
+            mShadeLog.logExpansionChanged("onHeightUpdated: fully collapsed.",
+                    mExpandedFraction, isExpanded(), mTracking, mExpansionDragDownAmountPx);
+        } else if (isFullyExpanded()) {
+            mShadeLog.logExpansionChanged("onHeightUpdated: fully expanded.",
+                    mExpandedFraction, isExpanded(), mTracking, mExpansionDragDownAmountPx);
+        }
         if (!mQsExpanded || mQsExpandImmediate || mIsExpanding && mQsExpandedWhenExpandingStarted) {
             // Updating the clock position will set the top padding which might
             // trigger a new panel height and re-position the clock.
             // This is a circular dependency and should be avoided, otherwise we'll have
             // a stack overflow.
             if (mStackScrollerMeasuringPass > 2) {
-                if (DEBUG_LOGCAT) Log.d(TAG, "Unstable notification panel height. Aborting.");
+                debugLog("Unstable notification panel height. Aborting.");
             } else {
                 positionClockAndNotifications();
             }
         }
-        if (mQsExpandImmediate || (mQsExpanded && !mQsTracking && mQsExpansionAnimator == null
-                && !mQsExpansionFromOverscroll)) {
+        // Below is true when QS are expanded and we swipe up from the same bottom of panel to
+        // close the whole shade with one motion. Also this will be always true when closing
+        // split shade as there QS are always expanded so every collapsing motion is motion from
+        // expanded QS to closed panel
+        boolean collapsingShadeFromExpandedQs = mQsExpanded && !mQsTracking
+                && mQsExpansionAnimator == null && !mQsExpansionFromOverscroll;
+        boolean goingBetweenClosedShadeAndExpandedQs =
+                mQsExpandImmediate || collapsingShadeFromExpandedQs;
+        // we don't want to update QS expansion when HUN is visible because then the whole shade is
+        // initially hidden, even though it has non-zero height
+        if (goingBetweenClosedShadeAndExpandedQs && !mHeadsUpManager.isTrackingHeadsUp()) {
             float qsExpansionFraction;
             if (mSplitShadeEnabled) {
                 qsExpansionFraction = 1;
@@ -3475,7 +3356,7 @@
             }
             float targetHeight = mQsMinExpansionHeight
                     + qsExpansionFraction * (mQsMaxExpansionHeight - mQsMinExpansionHeight);
-            setQsExpansion(targetHeight);
+            setQsExpansionHeight(targetHeight);
         }
         updateExpandedHeight(expandedHeight);
         updateHeader();
@@ -3577,9 +3458,7 @@
         return alpha;
     }
 
-    /**
-     * Hides the header when notifications are colliding with it.
-     */
+    /** Hides the header when notifications are colliding with it. */
     private void updateHeader() {
         if (mBarState == KEYGUARD) {
             mKeyguardStatusBarViewController.updateViewState();
@@ -3722,7 +3601,7 @@
                                 if (mAnimateAfterExpanding) {
                                     notifyExpandingStarted();
                                     beginJankMonitoring();
-                                    fling(0, true /* expand */);
+                                    fling(0  /* expand */);
                                 } else {
                                     setExpandedFraction(1f);
                                 }
@@ -3759,6 +3638,24 @@
 
     }
 
+    private void falsingAdditionalTapRequired() {
+        if (mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED) {
+            mTapAgainViewController.show();
+        } else {
+            mKeyguardIndicationController.showTransientIndication(
+                    R.string.notification_tap_again);
+        }
+
+        if (!mStatusBarStateController.isDozing()) {
+            mVibratorHelper.vibrate(
+                    Process.myUid(),
+                    mView.getContext().getPackageName(),
+                    ADDITIONAL_TAP_REQUIRED_VIBRATION_EFFECT,
+                    "falsing-additional-tap-required",
+                    TOUCH_VIBRATION_ATTRIBUTES);
+        }
+    }
+
     private void onTrackingStarted() {
         mFalsingCollector.onTrackingStarted(!mKeyguardStateController.canDismissLockScreen());
         endClosing();
@@ -3793,7 +3690,7 @@
 
     private void updateMaxHeadsUpTranslation() {
         mNotificationStackScrollLayoutController.setHeadsUpBoundaries(
-                getHeight(), mNavigationBarBottomHeight);
+                mView.getHeight(), mNavigationBarBottomHeight);
     }
 
     @VisibleForTesting
@@ -3838,7 +3735,8 @@
                 || !isTracking());
     }
 
-    public int getMaxPanelTransitionDistance() {
+    @VisibleForTesting
+    int getMaxPanelTransitionDistance() {
         // Traditionally the value is based on the number of notifications. On split-shade, we want
         // the required distance to be a specific and constant value, to make sure the expansion
         // motion has the expected speed. We also only want this on non-lockscreen for now.
@@ -3889,16 +3787,15 @@
         boolean wasRunning = mIsLaunchAnimationRunning;
         mIsLaunchAnimationRunning = running;
         if (wasRunning != mIsLaunchAnimationRunning) {
-            mPanelEventsEmitter.notifyLaunchingActivityChanged(running);
+            mShadeExpansionStateManager.notifyLaunchingActivityChanged(running);
         }
     }
 
     @VisibleForTesting
-    void setIsClosing(boolean isClosing) {
-        boolean wasClosing = isClosing();
-        mClosing = isClosing;
-        if (wasClosing != isClosing) {
-            mPanelEventsEmitter.notifyPanelCollapsingChanged(isClosing);
+    void setClosing(boolean isClosing) {
+        if (mClosing != isClosing) {
+            mClosing = isClosing;
+            mShadeExpansionStateManager.notifyPanelCollapsingChanged(isClosing);
         }
         mAmbientState.setIsClosing(isClosing);
     }
@@ -3910,10 +3807,6 @@
         }
     }
 
-    public boolean isDozing() {
-        return mDozing;
-    }
-
     public void setQsScrimEnabled(boolean qsScrimEnabled) {
         boolean changed = mQsScrimEnabled != qsScrimEnabled;
         mQsScrimEnabled = qsScrimEnabled;
@@ -3926,14 +3819,14 @@
         mKeyguardStatusViewController.dozeTimeTick();
     }
 
-    private boolean onMiddleClicked() {
+    private void onMiddleClicked() {
         switch (mBarState) {
             case KEYGUARD:
                 if (!mDozingOnDown) {
                     mShadeLog.v("onMiddleClicked on Keyguard, mDozingOnDown: false");
                     // Try triggering face auth, this "might" run. Check
                     // KeyguardUpdateMonitor#shouldListenForFace to see when face auth won't run.
-                    boolean didFaceAuthRun = mUpdateMonitor.requestFaceAuth(true,
+                    boolean didFaceAuthRun = mUpdateMonitor.requestFaceAuth(
                             FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED);
 
                     if (didFaceAuthRun) {
@@ -3948,14 +3841,12 @@
                         startUnlockHintAnimation();
                     }
                 }
-                return true;
+                break;
             case StatusBarState.SHADE_LOCKED:
                 if (!mQsExpanded) {
                     mStatusBarStateController.setState(KEYGUARD);
                 }
-                return true;
-            default:
-                return true;
+                break;
         }
     }
 
@@ -3991,6 +3882,7 @@
 
     public void setHeadsUpManager(HeadsUpManagerPhone headsUpManager) {
         mHeadsUpManager = headsUpManager;
+        mHeadsUpManager.addListener(mOnHeadsUpChangedListener);
         mHeadsUpTouchHelper = new HeadsUpTouchHelper(headsUpManager,
                 mNotificationStackScrollLayoutController.getHeadsUpCallback(),
                 NotificationPanelViewController.this);
@@ -4029,17 +3921,9 @@
         updateStatusBarIcons();
     }
 
-    /**
-     * @return whether the notifications are displayed full width and don't have any margins on
-     * the side.
-     */
-    public boolean isFullWidth() {
-        return mIsFullWidth;
-    }
-
     private void updateStatusBarIcons() {
         boolean showIconsWhenExpanded =
-                (isPanelVisibleBecauseOfHeadsUp() || isFullWidth())
+                (isPanelVisibleBecauseOfHeadsUp() || mIsFullWidth)
                         && getExpandedHeight() < getOpeningHeight();
         if (showIconsWhenExpanded && isOnKeyguard()) {
             showIconsWhenExpanded = false;
@@ -4054,10 +3938,7 @@
         return mBarState == KEYGUARD;
     }
 
-    /**
-     * Called when heads-up notification is being dragged up or down to indicate what's the starting
-     * height for shade motion
-     */
+    /** Called when a HUN is dragged up or down to indicate the starting height for shade motion. */
     public void setHeadsUpDraggingStartingHeight(int startHeight) {
         mHeadsUpStartHeight = startHeight;
         float scrimMinFraction;
@@ -4111,25 +3992,18 @@
         setLaunchingAffordance(false);
     }
 
-    /**
-     * Set whether we are currently launching an affordance. This is currently only set when
-     * launched via a camera gesture.
-     */
+    /** Set whether we are currently launching an affordance (i.e. camera gesture). */
     private void setLaunchingAffordance(boolean launchingAffordance) {
         mLaunchingAffordance = launchingAffordance;
         mKeyguardBypassController.setLaunchingAffordance(launchingAffordance);
     }
 
-    /**
-     * Return true when a bottom affordance is launching an occluded activity with a splash screen.
-     */
+    /** Returns whether a bottom affordance is launching an occluded activity with splash screen. */
     public boolean isLaunchingAffordanceWithPreview() {
         return mLaunchingAffordance;
     }
 
-    /**
-     * Whether the camera application can be launched for the camera launch gesture.
-     */
+    /** Whether the camera application can be launched by the camera launch gesture. */
     public boolean canCameraGestureBeLaunched() {
         return mCameraGestureHelper.canCameraGestureBeLaunched(mBarState);
     }
@@ -4142,22 +4016,19 @@
                 && mHeadsUpAppearanceController.shouldBeVisible()) {
             return false;
         }
-        return !isFullWidth() || !mShowIconsWhenExpanded;
+        return !mIsFullWidth || !mShowIconsWhenExpanded;
     }
 
-    public final QS.ScrollListener mScrollListener = new QS.ScrollListener() {
-        @Override
-        public void onQsPanelScrollChanged(int scrollY) {
-            mLargeScreenShadeHeaderController.setQsScrollY(scrollY);
-            if (scrollY > 0 && !mQsFullyExpanded) {
-                if (DEBUG_LOGCAT) Log.d(TAG, "Scrolling while not expanded. Forcing expand");
-                // If we are scrolling QS, we should be fully expanded.
-                expandWithQs();
-            }
+    private void onQsPanelScrollChanged(int scrollY) {
+        mLargeScreenShadeHeaderController.setQsScrollY(scrollY);
+        if (scrollY > 0 && !mQsFullyExpanded) {
+            debugLog("Scrolling while not expanded. Forcing expand");
+            // If we are scrolling QS, we should be fully expanded.
+            expandWithQs();
         }
-    };
+    }
 
-    private final FragmentListener mFragmentListener = new FragmentListener() {
+    private final class QsFragmentListener implements FragmentListener {
         @Override
         public void onFragmentViewCreated(String tag, Fragment fragment) {
             mQs = (QS) fragment;
@@ -4174,7 +4045,7 @@
                         final int height = bottom - top;
                         final int oldHeight = oldBottom - oldTop;
                         if (height != oldHeight) {
-                            mHeightListener.onQsHeightChanged();
+                            onQsHeightChanged();
                         }
                     });
             mQs.setCollapsedMediaVisibilityChangedListener((visible) -> {
@@ -4187,7 +4058,7 @@
             mLockscreenShadeTransitionController.setQS(mQs);
             mShadeTransitionController.setQs(mQs);
             mNotificationStackScrollLayoutController.setQsHeader((ViewGroup) mQs.getHeader());
-            mQs.setScrollListener(mScrollListener);
+            mQs.setScrollListener(mQsScrollListener);
             updateQsExpansion();
         }
 
@@ -4200,7 +4071,7 @@
                 mQs = null;
             }
         }
-    };
+    }
 
     private void animateNextNotificationBounds(long duration, long delay) {
         mAnimateNextNotificationBounds = true;
@@ -4223,8 +4094,8 @@
     /**
      * Sets the dozing state.
      *
-     * @param dozing              {@code true} when dozing.
-     * @param animate             if transition should be animated.
+     * @param dozing  {@code true} when dozing.
+     * @param animate if transition should be animated.
      */
     public void setDozing(boolean dozing, boolean animate) {
         if (dozing == mDozing) return;
@@ -4290,13 +4161,7 @@
         mKeyguardStatusViewController.setStatusAccessibilityImportance(mode);
     }
 
-    /**
-     * TODO: this should be removed.
-     * It's not correct to pass this view forward because other classes will end up adding
-     * children to it. Theme will be out of sync.
-     *
-     * @return bottom area view
-     */
+    //TODO(b/254875405): this should be removed.
     public KeyguardBottomAreaView getKeyguardBottomAreaView() {
         return mKeyguardBottomArea;
     }
@@ -4325,11 +4190,8 @@
         mHeadsUpAppearanceController = headsUpAppearanceController;
     }
 
-    /**
-     * Starts the animation before we dismiss Keyguard, i.e. an disappearing animation on the
-     * security view of the bouncer.
-     */
-    public void onBouncerPreHideAnimation() {
+    /** Called before animating Keyguard dismissal, i.e. the animation dismissing the bouncer. */
+    public void startBouncerPreHideAnimation() {
         if (mKeyguardQsUserSwitchController != null) {
             mKeyguardQsUserSwitchController.setKeyguardQsUserSwitchVisibility(
                     mBarState,
@@ -4346,9 +4208,7 @@
         }
     }
 
-    /**
-     * Updates the views to the initial state for the fold to AOD animation
-     */
+    /** Updates the views to the initial state for the fold to AOD animation. */
     public void prepareFoldToAodAnimation() {
         // Force show AOD UI even if we are not locked
         showAodUi();
@@ -4364,40 +4224,37 @@
     /**
      * Starts fold to AOD animation.
      *
-     * @param startAction invoked when the animation starts.
-     * @param endAction invoked when the animation finishes, also if it was cancelled.
+     * @param startAction  invoked when the animation starts.
+     * @param endAction    invoked when the animation finishes, also if it was cancelled.
      * @param cancelAction invoked when the animation is cancelled, before endAction.
      */
     public void startFoldToAodAnimation(Runnable startAction, Runnable endAction,
             Runnable cancelAction) {
         mView.animate()
-            .translationX(0)
-            .alpha(1f)
-            .setDuration(ANIMATION_DURATION_FOLD_TO_AOD)
-            .setInterpolator(EMPHASIZED_DECELERATE)
-            .setListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationStart(Animator animation) {
-                    startAction.run();
-                }
+                .translationX(0)
+                .alpha(1f)
+                .setDuration(ANIMATION_DURATION_FOLD_TO_AOD)
+                .setInterpolator(EMPHASIZED_DECELERATE)
+                .setListener(new AnimatorListenerAdapter() {
+                    @Override
+                    public void onAnimationStart(Animator animation) {
+                        startAction.run();
+                    }
 
-                @Override
-                public void onAnimationCancel(Animator animation) {
-                    cancelAction.run();
-                }
+                    @Override
+                    public void onAnimationCancel(Animator animation) {
+                        cancelAction.run();
+                    }
 
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    endAction.run();
-                }
-            }).setUpdateListener(anim -> {
-                mKeyguardStatusViewController.animateFoldToAod(anim.getAnimatedFraction());
-            }).start();
+                    @Override
+                    public void onAnimationEnd(Animator animation) {
+                        endAction.run();
+                    }
+                }).setUpdateListener(anim -> mKeyguardStatusViewController.animateFoldToAod(
+                        anim.getAnimatedFraction())).start();
     }
 
-    /**
-     * Cancels fold to AOD transition and resets view state
-     */
+    /** Cancels fold to AOD transition and resets view state. */
     public void cancelFoldToAodAnimation() {
         cancelAnimation();
         resetAlpha();
@@ -4441,42 +4298,11 @@
         }
     }
 
-    public boolean hasActiveClearableNotifications() {
-        return mNotificationStackScrollLayoutController.hasActiveClearableNotifications(ROWS_ALL);
-    }
 
     public RemoteInputController.Delegate createRemoteInputDelegate() {
         return mNotificationStackScrollLayoutController.createDelegate();
     }
 
-    /**
-     * Updates the notification views' sections and status bar icons. This is
-     * triggered by the NotificationPresenter whenever there are changes to the underlying
-     * notification data being displayed. In the new notification pipeline, this is handled in
-     * {@link ShadeViewManager}.
-     */
-    public void updateNotificationViews() {
-        mNotificationStackScrollLayoutController.updateFooter();
-
-        mNotificationIconAreaController.updateNotificationIcons(createVisibleEntriesList());
-    }
-
-    private List<ListEntry> createVisibleEntriesList() {
-        List<ListEntry> entries = new ArrayList<>(
-                mNotificationStackScrollLayoutController.getChildCount());
-        for (int i = 0; i < mNotificationStackScrollLayoutController.getChildCount(); i++) {
-            View view = mNotificationStackScrollLayoutController.getChildAt(i);
-            if (view instanceof ExpandableNotificationRow) {
-                entries.add(((ExpandableNotificationRow) view).getEntry());
-            }
-        }
-        return entries;
-    }
-
-    public void onUpdateRowStates() {
-        mNotificationStackScrollLayoutController.onUpdateRowStates();
-    }
-
     public boolean hasPulsingNotifications() {
         return mNotificationListContainer.hasPulsingNotifications();
     }
@@ -4493,16 +4319,6 @@
         mNotificationStackScrollLayoutController.runAfterAnimationFinished(r);
     }
 
-    private Runnable mHideExpandedRunnable;
-    private final Runnable mMaybeHideExpandedRunnable = new Runnable() {
-        @Override
-        public void run() {
-            if (getExpansionFraction() == 0.0f) {
-                mView.post(mHideExpandedRunnable);
-            }
-        }
-    };
-
     /**
      * Initialize objects instead of injecting to avoid circular dependencies.
      *
@@ -4512,7 +4328,9 @@
             CentralSurfaces centralSurfaces,
             Runnable hideExpandedRunnable,
             NotificationShelfController notificationShelfController) {
-        setCentralSurfaces(centralSurfaces);
+        // TODO(b/254859580): this can be injected.
+        mCentralSurfaces = centralSurfaces;
+
         mHideExpandedRunnable = hideExpandedRunnable;
         mNotificationStackScrollLayoutController.setShelfController(notificationShelfController);
         mNotificationShelfController = notificationShelfController;
@@ -4520,10 +4338,6 @@
         updateMaxDisplayedNotifications(true);
     }
 
-    public void setAlpha(float alpha) {
-        mView.setAlpha(alpha);
-    }
-
     public void resetTranslation() {
         mView.setTranslationX(0f);
     }
@@ -4542,22 +4356,14 @@
         ViewGroupFadeHelper.reset(mView);
     }
 
-    public void addOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener listener) {
+    void addOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener listener) {
         mView.getViewTreeObserver().addOnGlobalLayoutListener(listener);
     }
 
-    public void removeOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener listener) {
+    void removeOnGlobalLayoutListener(ViewTreeObserver.OnGlobalLayoutListener listener) {
         mView.getViewTreeObserver().removeOnGlobalLayoutListener(listener);
     }
 
-    public MyOnHeadsUpChangedListener getOnHeadsUpChangedListener() {
-        return mOnHeadsUpChangedListener;
-    }
-
-    public int getHeight() {
-        return mView.getHeight();
-    }
-
     public void setHeaderDebugInfo(String text) {
         if (DEBUG_DRAWABLE) mHeaderDebugInfo = text;
     }
@@ -4566,10 +4372,6 @@
         mConfigurationListener.onThemeChanged();
     }
 
-    private OnLayoutChangeListener createLayoutChangeListener() {
-        return new OnLayoutChangeListener();
-    }
-
     @VisibleForTesting
     TouchHandler createTouchHandler() {
         return new TouchHandler();
@@ -4624,10 +4426,6 @@
                 }
             };
 
-    private OnConfigurationChangedListener createOnConfigurationChangedListener() {
-        return new OnConfigurationChangedListener();
-    }
-
     public NotificationStackScrollLayoutController getNotificationStackScrollLayoutController() {
         return mNotificationStackScrollLayoutController;
     }
@@ -4668,13 +4466,7 @@
         );
     }
 
-    private void unregisterSettingsChangeListener() {
-        mContentResolver.unregisterContentObserver(mSettingsChangeObserver);
-    }
-
-    /**
-     * Updates notification panel-specific flags on {@link SysUiState}.
-     */
+    /** Updates notification panel-specific flags on {@link SysUiState}. */
     public void updateSystemUiStateFlags() {
         if (SysUiState.DEBUG) {
             Log.d(TAG, "Updating panel sysui state flags: fullyExpanded="
@@ -4686,8 +4478,10 @@
                 .commitUpdate(mDisplayId);
     }
 
-    private void logf(String fmt, Object... args) {
-        Log.v(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args));
+    private void debugLog(String fmt, Object... args) {
+        if (DEBUG_LOGCAT) {
+            Log.d(TAG, (mViewName != null ? (mViewName + ": ") : "") + String.format(fmt, args));
+        }
     }
 
     @VisibleForTesting
@@ -4734,8 +4528,6 @@
 
     private void startOpening(MotionEvent event) {
         updatePanelExpansionAndVisibility();
-        // Reset at start so haptic can be triggered as soon as panel starts to open.
-        mHasVibratedOnOpen = false;
         //TODO: keyguard opens QS a different way; log that too?
 
         // Log the position of the swipe that opened the panel
@@ -4752,8 +4544,9 @@
     /**
      * Maybe vibrate as panel is opened.
      *
-     * @param openingWithTouch Whether the panel is being opened with touch. If the panel is instead
-     * being opened programmatically (such as by the open panel gesture), we always play haptic.
+     * @param openingWithTouch Whether the panel is being opened with touch. If the panel is
+     *                         instead being opened programmatically (such as by the open panel
+     *                         gesture), we always play haptic.
      */
     private void maybeVibrateOnOpening(boolean openingWithTouch) {
         if (mVibrateOnOpening) {
@@ -4850,10 +4643,10 @@
                 mUpdateFlingVelocity = vel;
             }
         } else if (!mCentralSurfaces.isBouncerShowing()
-                && !mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()
+                && !mStatusBarKeyguardViewManager.isShowingAlternateAuth()
                 && !mKeyguardStateController.isKeyguardGoingAway()) {
-            boolean expands = onEmptySpaceClick();
-            onTrackingStopped(expands);
+            onEmptySpaceClick();
+            onTrackingStopped(true);
         }
         mVelocityTracker.clear();
     }
@@ -4865,7 +4658,7 @@
 
     private void endClosing() {
         if (mClosing) {
-            setIsClosing(false);
+            setClosing(false);
             onClosingFinished();
         }
     }
@@ -4900,7 +4693,7 @@
             boolean expandBecauseOfFalsing) {
         float target = expand ? getMaxPanelHeight() : 0;
         if (!expand) {
-            setIsClosing(true);
+            setClosing(true);
         }
         flingToHeight(vel, expand, target, collapseSpeedUpFactor, expandBecauseOfFalsing);
     }
@@ -4919,10 +4712,12 @@
         animator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
         animator.addListener(new AnimatorListenerAdapter() {
             private boolean mCancelled;
+
             @Override
             public void onAnimationCancel(Animator animation) {
                 mCancelled = true;
             }
+
             @Override
             public void onAnimationEnd(Animator animation) {
                 mIsSpringBackAnimation = false;
@@ -4933,13 +4728,9 @@
         animator.start();
     }
 
-    public String getName() {
-        return mViewName;
-    }
-
     @VisibleForTesting
     void setExpandedHeight(float height) {
-        if (DEBUG) logf("setExpandedHeight(%.1f)", height);
+        debugLog("setExpandedHeight(%.1f)", height);
         setExpandedHeightInternal(height);
     }
 
@@ -4970,7 +4761,7 @@
         if (isNaN(h)) {
             Log.wtf(TAG, "ExpandedHeight set to NaN");
         }
-        mNotificationShadeWindowController.batchApplyWindowLayoutParams(()-> {
+        mNotificationShadeWindowController.batchApplyWindowLayoutParams(() -> {
             if (mExpandLatencyTracking && h != 0f) {
                 DejankUtils.postAfterTraversal(
                         () -> mLatencyTracker.onActionEnd(LatencyTracker.ACTION_EXPAND_PANEL));
@@ -5031,12 +4822,12 @@
         return mExpandedHeight;
     }
 
-    public float getExpandedFraction() {
+    private float getExpandedFraction() {
         return mExpandedFraction;
     }
 
     public boolean isFullyExpanded() {
-        return mExpandedHeight >= getMaxPanelHeight();
+        return mExpandedHeight >= getMaxPanelTransitionDistance();
     }
 
     public boolean isFullyCollapsed() {
@@ -5047,10 +4838,6 @@
         return mClosing || mIsLaunchAnimationRunning;
     }
 
-    public boolean isFlinging() {
-        return mIsFlinging;
-    }
-
     public boolean isTracking() {
         return mTracking;
     }
@@ -5161,7 +4948,7 @@
     /**
      * Create an animator that can also overshoot
      *
-     * @param targetHeight the target height
+     * @param targetHeight    the target height
      * @param overshootAmount the amount of overshoot desired
      */
     private ValueAnimator createHeightAnimator(float targetHeight, float overshootAmount) {
@@ -5197,8 +4984,7 @@
      */
     public void updatePanelExpansionAndVisibility() {
         mShadeExpansionStateManager.onPanelExpansionChanged(
-                mExpandedFraction, isExpanded(),
-                mTracking, mExpansionDragDownAmountPx);
+                mExpandedFraction, isExpanded(), mTracking, mExpansionDragDownAmountPx);
         updateVisibility();
     }
 
@@ -5211,16 +4997,11 @@
                 && !mIsSpringBackAnimation;
     }
 
-    /**
-     * Gets called when the user performs a click anywhere in the empty area of the panel.
-     *
-     * @return whether the panel will be expanded after the action performed by this method
-     */
-    private boolean onEmptySpaceClick() {
-        if (mHintAnimationRunning) {
-            return true;
+    /** Called when the user performs a click anywhere in the empty area of the panel. */
+    private void onEmptySpaceClick() {
+        if (!mHintAnimationRunning)  {
+            onMiddleClicked();
         }
-        return onMiddleClicked();
     }
 
     @VisibleForTesting
@@ -5237,7 +5018,7 @@
 
     /** Returns the NotificationPanelView. */
     public ViewGroup getView() {
-        // TODO: remove this method, or at least reduce references to it.
+        // TODO(b/254878364): remove this method, or at least reduce references to it.
         return mView;
     }
 
@@ -5277,12 +5058,11 @@
         return mShadeExpansionStateManager;
     }
 
-    private class OnHeightChangedListener implements ExpandableView.OnHeightChangedListener {
+    private final class NsslHeightChangedListener implements
+            ExpandableView.OnHeightChangedListener {
         @Override
         public void onHeightChanged(ExpandableView view, boolean needsAnimation) {
-
-            // Block update if we are in quick settings and just the top padding changed
-            // (i.e. view == null).
+            // Block update if we are in QS and just the top padding changed (i.e. view == null).
             if (view == null && mQsExpanded) {
                 return;
             }
@@ -5306,26 +5086,22 @@
         }
 
         @Override
-        public void onReset(ExpandableView view) {
+        public void onReset(ExpandableView view) {}
+    }
+
+    private void collapseOrExpand() {
+        onQsExpansionStarted();
+        if (mQsExpanded) {
+            flingSettings(0 /* vel */, FLING_COLLAPSE, null /* onFinishRunnable */,
+                    true /* isClick */);
+        } else if (isQsExpansionEnabled()) {
+            mLockscreenGestureLogger.write(MetricsEvent.ACTION_SHADE_QS_TAP, 0, 0);
+            flingSettings(0 /* vel */, FLING_EXPAND, null /* onFinishRunnable */,
+                    true /* isClick */);
         }
     }
 
-    private class CollapseExpandAction implements Runnable {
-        @Override
-        public void run() {
-            onQsExpansionStarted();
-            if (mQsExpanded) {
-                flingSettings(0 /* vel */, FLING_COLLAPSE, null /* onFinishRunnable */,
-                        true /* isClick */);
-            } else if (isQsExpansionEnabled()) {
-                mLockscreenGestureLogger.write(MetricsEvent.ACTION_SHADE_QS_TAP, 0, 0);
-                flingSettings(0 /* vel */, FLING_EXPAND, null /* onFinishRunnable */,
-                        true /* isClick */);
-            }
-        }
-    }
-
-    private class OnOverscrollTopChangedListener implements
+    private final class NsslOverscrollTopChangedListener implements
             NotificationStackScrollLayout.OnOverscrollTopChangedListener {
         @Override
         public void onOverscrollTopChanged(float amount, boolean isRubberbanded) {
@@ -5342,7 +5118,7 @@
             mQsExpansionFromOverscroll = rounded != 0f;
             mLastOverscroll = rounded;
             updateQsState();
-            setQsExpansion(mQsMinExpansionHeight + rounded);
+            setQsExpansionHeight(mQsMinExpansionHeight + rounded);
         }
 
         @Override
@@ -5359,7 +5135,7 @@
                 // make sure we can expand
                 setOverScrolling(false);
             }
-            setQsExpansion(mQsExpansionHeight);
+            setQsExpansionHeight(mQsExpansionHeight);
             boolean canExpand = isQsExpansionEnabled();
             flingSettings(!canExpand && open ? 0f : velocity,
                     open && canExpand ? FLING_EXPAND : FLING_COLLAPSE, () -> {
@@ -5369,27 +5145,16 @@
         }
     }
 
-    private class DynamicPrivacyControlListener implements DynamicPrivacyController.Listener {
-        @Override
-        public void onDynamicPrivacyChanged() {
-            // Do not request animation when pulsing or waking up, otherwise the clock wiill be out
-            // of sync with the notification panel.
-            if (mLinearDarkAmount != 0) {
-                return;
-            }
-            mAnimateNextPositionUpdate = true;
+    private void onDynamicPrivacyChanged() {
+        // Do not request animation when pulsing or waking up, otherwise the clock will be out
+        // of sync with the notification panel.
+        if (mLinearDarkAmount != 0) {
+            return;
         }
+        mAnimateNextPositionUpdate = true;
     }
 
-    private class OnEmptySpaceClickListener implements
-            NotificationStackScrollLayout.OnEmptySpaceClickListener {
-        @Override
-        public void onEmptySpaceClicked(float x, float y) {
-            onEmptySpaceClick();
-        }
-    }
-
-    private class MyOnHeadsUpChangedListener implements OnHeadsUpChangedListener {
+    private final class ShadeHeadsUpChangedListener implements OnHeadsUpChangedListener {
         @Override
         public void onHeadsUpPinnedModeChanged(final boolean inPinnedMode) {
             if (inPinnedMode) {
@@ -5429,32 +5194,31 @@
         }
     }
 
-    private class HeightListener implements QS.HeightListener {
-        public void onQsHeightChanged() {
-            mQsMaxExpansionHeight = mQs != null ? mQs.getDesiredHeight() : 0;
-            if (mQsExpanded && mQsFullyExpanded) {
-                mQsExpansionHeight = mQsMaxExpansionHeight;
-                requestScrollerTopPaddingUpdate(false /* animate */);
-                updateExpandedHeightToMaxHeight();
-            }
-            if (mAccessibilityManager.isEnabled()) {
-                mView.setAccessibilityPaneTitle(determineAccessibilityPaneTitle());
-            }
-            mNotificationStackScrollLayoutController.setMaxTopPadding(mQsMaxExpansionHeight);
+    private void onQsHeightChanged() {
+        mQsMaxExpansionHeight = mQs != null ? mQs.getDesiredHeight() : 0;
+        if (mQsExpanded && mQsFullyExpanded) {
+            mQsExpansionHeight = mQsMaxExpansionHeight;
+            requestScrollerTopPaddingUpdate(false /* animate */);
+            updateExpandedHeightToMaxHeight();
         }
+        if (mAccessibilityManager.isEnabled()) {
+            mView.setAccessibilityPaneTitle(determineAccessibilityPaneTitle());
+        }
+        mNotificationStackScrollLayoutController.setMaxTopPadding(mQsMaxExpansionHeight);
     }
 
-    private class ConfigurationListener implements ConfigurationController.ConfigurationListener {
+    private final class ConfigurationListener implements
+            ConfigurationController.ConfigurationListener {
         @Override
         public void onThemeChanged() {
-            if (DEBUG_LOGCAT) Log.d(TAG, "onThemeChanged");
+            debugLog("onThemeChanged");
             reInflateViews();
         }
 
         @Override
         public void onSmallestScreenWidthChanged() {
             Trace.beginSection("onSmallestScreenWidthChanged");
-            if (DEBUG_LOGCAT) Log.d(TAG, "onSmallestScreenWidthChanged");
+            debugLog("onSmallestScreenWidthChanged");
 
             // Can affect multi-user switcher visibility as it depends on screen size by default:
             // it is enabled only for devices with large screens (see config_keyguardUserSwitcher)
@@ -5471,27 +5235,26 @@
 
         @Override
         public void onDensityOrFontScaleChanged() {
-            if (DEBUG_LOGCAT) Log.d(TAG, "onDensityOrFontScaleChanged");
+            debugLog("onDensityOrFontScaleChanged");
             reInflateViews();
         }
     }
 
-    private class SettingsChangeObserver extends ContentObserver {
-
+    private final class SettingsChangeObserver extends ContentObserver {
         SettingsChangeObserver(Handler handler) {
             super(handler);
         }
 
         @Override
         public void onChange(boolean selfChange) {
-            if (DEBUG_LOGCAT) Log.d(TAG, "onSettingsChanged");
+            debugLog("onSettingsChanged");
 
             // Can affect multi-user switcher visibility
             reInflateViews();
         }
     }
 
-    private class StatusBarStateListener implements StateListener {
+    private final class StatusBarStateListener implements StateListener {
         @Override
         public void onStateChanged(int statusBarState) {
             boolean goingToFullShade = mStatusBarStateController.goingToFullShade();
@@ -5552,7 +5315,7 @@
                 }
             } else {
                 // this else branch means we are doing one of:
-                //  - from KEYGUARD and SHADE (but not expanded shade)
+                //  - from KEYGUARD to SHADE (but not fully expanded as when swiping from the top)
                 //  - from SHADE to KEYGUARD
                 //  - from SHADE_LOCKED to SHADE
                 //  - getting notified again about the current SHADE or KEYGUARD state
@@ -5647,21 +5410,19 @@
         setExpandedFraction(1f);
     }
 
-    /**
-     * Sets the overstretch amount in raw pixels when dragging down.
-     */
-    public void setOverStrechAmount(float amount) {
+    /** Sets the overstretch amount in raw pixels when dragging down. */
+    public void setOverStretchAmount(float amount) {
         float progress = amount / mView.getHeight();
-        float overstretch = Interpolators.getOvershootInterpolation(progress);
-        mOverStretchAmount = overstretch * mMaxOverscrollAmountForPulse;
+        float overStretch = Interpolators.getOvershootInterpolation(progress);
+        mOverStretchAmount = overStretch * mMaxOverscrollAmountForPulse;
         positionClockAndNotifications(true /* forceUpdate */);
     }
 
-    private class OnAttachStateChangeListener implements View.OnAttachStateChangeListener {
+    private final class ShadeAttachStateChangeListener implements View.OnAttachStateChangeListener {
         @Override
         public void onViewAttachedToWindow(View v) {
             mFragmentService.getFragmentHostManager(mView)
-                    .addTagListener(QS.TAG, mFragmentListener);
+                    .addTagListener(QS.TAG, mQsFragmentListener);
             mStatusBarStateController.addCallback(mStatusBarStateListener);
             mStatusBarStateListener.onStateChanged(mStatusBarStateController.getState());
             mConfigurationController.addCallback(mConfigurationListener);
@@ -5676,16 +5437,16 @@
 
         @Override
         public void onViewDetachedFromWindow(View v) {
-            unregisterSettingsChangeListener();
+            mContentResolver.unregisterContentObserver(mSettingsChangeObserver);
             mFragmentService.getFragmentHostManager(mView)
-                    .removeTagListener(QS.TAG, mFragmentListener);
+                    .removeTagListener(QS.TAG, mQsFragmentListener);
             mStatusBarStateController.removeCallback(mStatusBarStateListener);
             mConfigurationController.removeCallback(mConfigurationListener);
             mFalsingManager.removeTapListener(mFalsingTapListener);
         }
     }
 
-    private final class OnLayoutChangeListener implements View.OnLayoutChangeListener {
+    private final class ShadeLayoutChangeListener implements View.OnLayoutChangeListener {
         @Override
         public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
                 int oldTop, int oldRight, int oldBottom) {
@@ -5694,7 +5455,7 @@
             mHasLayoutedSinceDown = true;
             if (mUpdateFlingOnLayout) {
                 abortAnimations();
-                fling(mUpdateFlingVelocity, true /* expands */);
+                fling(mUpdateFlingVelocity);
                 mUpdateFlingOnLayout = false;
             }
             updateMaxDisplayedNotifications(!shouldAvoidChangingNotificationsCount());
@@ -5721,21 +5482,18 @@
                     startQsSizeChangeAnimation(oldMaxHeight, mQsMaxExpansionHeight);
                 }
             } else if (!mQsExpanded && mQsExpansionAnimator == null) {
-                setQsExpansion(mQsMinExpansionHeight + mLastOverscroll);
+                setQsExpansionHeight(mQsMinExpansionHeight + mLastOverscroll);
             } else {
                 mShadeLog.v("onLayoutChange: qs expansion not set");
             }
             updateExpandedHeight(getExpandedHeight());
             updateHeader();
 
-            // If we are running a size change animation, the animation takes care of the height of
-            // the container. However, if we are not animating, we always need to make the QS
-            // container
-            // the desired height so when closing the QS detail, it stays smaller after the size
-            // change
-            // animation is finished but the detail view is still being animated away (this
-            // animation
-            // takes longer than the size change animation).
+            // If we are running a size change animation, the animation takes care of the height
+            // of the container. However, if we are not animating, we always need to make the QS
+            // container the desired height so when closing the QS detail, it stays smaller after
+            // the size change animation is finished but the detail view is still being animated
+            // away (this animation takes longer than the size change animation).
             if (mQsSizeChangeAnimator == null && mQs != null) {
                 mQs.setHeightOverride(mQs.getDesiredHeight());
             }
@@ -5761,13 +5519,12 @@
         }
     }
 
-    private class DebugDrawable extends Drawable {
-
+    private final class DebugDrawable extends Drawable {
         private final Set<Integer> mDebugTextUsedYPositions = new HashSet<>();
         private final Paint mDebugPaint = new Paint();
 
         @Override
-        public void draw(@androidx.annotation.NonNull @NonNull Canvas canvas) {
+        public void draw(@NonNull Canvas canvas) {
             mDebugTextUsedYPositions.clear();
 
             mDebugPaint.setColor(Color.RED);
@@ -5845,18 +5602,17 @@
         }
     }
 
-    private class OnApplyWindowInsetsListener implements View.OnApplyWindowInsetsListener {
-        public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) {
-            // the same types of insets that are handled in NotificationShadeWindowView
-            int insetTypes = WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout();
-            Insets combinedInsets = insets.getInsetsIgnoringVisibility(insetTypes);
-            mDisplayTopInset = combinedInsets.top;
-            mDisplayRightInset = combinedInsets.right;
+    @NonNull
+    private WindowInsets onApplyShadeWindowInsets(WindowInsets insets) {
+        // the same types of insets that are handled in NotificationShadeWindowView
+        int insetTypes = WindowInsets.Type.systemBars() | WindowInsets.Type.displayCutout();
+        Insets combinedInsets = insets.getInsetsIgnoringVisibility(insetTypes);
+        mDisplayTopInset = combinedInsets.top;
+        mDisplayRightInset = combinedInsets.right;
 
-            mNavigationBarBottomHeight = insets.getStableInsetBottom();
-            updateMaxHeadsUpTranslation();
-            return insets;
-        }
+        mNavigationBarBottomHeight = insets.getStableInsetBottom();
+        updateMaxHeadsUpTranslation();
+        return insets;
     }
 
     /** Removes any pending runnables that would collapse the panel. */
@@ -5864,9 +5620,6 @@
         mView.removeCallbacks(mMaybeHideExpandedRunnable);
     }
 
-    @PanelState
-    private int mCurrentPanelState = STATE_CLOSED;
-
     private void onPanelStateChanged(@PanelState int state) {
         updateQSExpansionEnabledAmbient();
 
@@ -5903,6 +5656,11 @@
     }
 
     @VisibleForTesting
+    StateListener getStatusBarStateListener() {
+        return mStatusBarStateListener;
+    }
+
+    @VisibleForTesting
     boolean isHintAnimationRunning() {
         return mHintAnimationRunning;
     }
@@ -5917,50 +5675,13 @@
         }
     }
 
-    @SysUISingleton
-    static class PanelEventsEmitter implements NotifPanelEvents {
-
-        private final ListenerSet<Listener> mListeners = new ListenerSet<>();
-
-        @Inject
-        PanelEventsEmitter() {
-        }
-
-        @Override
-        public void registerListener(@androidx.annotation.NonNull @NonNull Listener listener) {
-            mListeners.addIfAbsent(listener);
-        }
-
-        @Override
-        public void unregisterListener(@androidx.annotation.NonNull @NonNull Listener listener) {
-            mListeners.remove(listener);
-        }
-
-        private void notifyLaunchingActivityChanged(boolean isLaunchingActivity) {
-            for (Listener cb : mListeners) {
-                cb.onLaunchingActivityChanged(isLaunchingActivity);
-            }
-        }
-
-        private void notifyPanelCollapsingChanged(boolean isCollapsing) {
-            for (NotifPanelEvents.Listener cb : mListeners) {
-                cb.onPanelCollapsingChanged(isCollapsing);
-            }
-        }
-
-        private void notifyExpandImmediateChange(boolean expandImmediateEnabled) {
-            for (NotifPanelEvents.Listener cb : mListeners) {
-                cb.onExpandImmediateChanged(expandImmediateEnabled);
-            }
-        }
-    }
-
     /** Handles MotionEvents for the Shade. */
     public final class TouchHandler implements View.OnTouchListener {
         private long mLastTouchDownTime = -1L;
 
-        /** @see ViewGroup#onInterceptTouchEvent(MotionEvent)  */
+        /** @see ViewGroup#onInterceptTouchEvent(MotionEvent) */
         public boolean onInterceptTouchEvent(MotionEvent event) {
+            mShadeLog.logMotionEvent(event, "NPVC onInterceptTouchEvent");
             if (SPEW_LOGCAT) {
                 Log.v(TAG,
                         "NPVC onInterceptTouchEvent (" + event.getId() + "): (" + event.getX()
@@ -5973,6 +5694,8 @@
             // Do not let touches go to shade or QS if the bouncer is visible,
             // but still let user swipe down to expand the panel, dismissing the bouncer.
             if (mCentralSurfaces.isBouncerShowing()) {
+                mShadeLog.v("NotificationPanelViewController MotionEvent intercepted: "
+                        + "bouncer is showing");
                 return true;
             }
             if (mCommandQueue.panelsEnabled()
@@ -5980,15 +5703,21 @@
                     && mHeadsUpTouchHelper.onInterceptTouchEvent(event)) {
                 mMetricsLogger.count(COUNTER_PANEL_OPEN, 1);
                 mMetricsLogger.count(COUNTER_PANEL_OPEN_PEEK, 1);
+                mShadeLog.v("NotificationPanelViewController MotionEvent intercepted: "
+                        + "HeadsUpTouchHelper");
                 return true;
             }
             if (!shouldQuickSettingsIntercept(mDownX, mDownY, 0)
                     && mPulseExpansionHandler.onInterceptTouchEvent(event)) {
+                mShadeLog.v("NotificationPanelViewController MotionEvent intercepted: "
+                        + "PulseExpansionHandler");
                 return true;
             }
 
             if (!isFullyCollapsed() && onQsIntercept(event)) {
-                if (DEBUG_LOGCAT) Log.d(TAG, "onQsIntercept true");
+                debugLog("onQsIntercept true");
+                mShadeLog.v("NotificationPanelViewController MotionEvent intercepted: "
+                        + "QsIntercept");
                 return true;
             }
             if (mInstantExpanding || !mNotificationsDragEnabled || mTouchDisabled || (mMotionAborted
@@ -6019,6 +5748,9 @@
                     if (mAnimatingOnDown && mClosing && !mHintAnimationRunning) {
                         cancelHeightAnimator();
                         mTouchSlopExceeded = true;
+                        mShadeLog.v("NotificationPanelViewController MotionEvent intercepted:"
+                                + " mAnimatingOnDown: true, mClosing: true, mHintAnimationRunning:"
+                                + " false");
                         return true;
                     }
                     mInitialExpandY = y;
@@ -6063,6 +5795,8 @@
                                 && hAbs > Math.abs(x - mInitialExpandX)) {
                             cancelHeightAnimator();
                             startExpandMotion(x, y, true /* startTracking */, mExpandedHeight);
+                            mShadeLog.v("NotificationPanelViewController MotionEvent"
+                                    + " intercepted: startExpandMotion");
                             return true;
                         }
                     }
@@ -6158,7 +5892,7 @@
                 mShadeLog.logMotionEvent(event, "onTouch: touch ignored due to instant expanding");
                 return false;
             }
-            if (mTouchDisabled  && event.getActionMasked() != MotionEvent.ACTION_CANCEL) {
+            if (mTouchDisabled && event.getActionMasked() != MotionEvent.ACTION_CANCEL) {
                 mShadeLog.logMotionEvent(event, "onTouch: non-cancel action, touch disabled");
                 return false;
             }
@@ -6191,7 +5925,6 @@
              *
              * Flinging is also enabled in order to open or close the shade.
              */
-
             int pointerIndex = event.findPointerIndex(mTrackingPointer);
             if (pointerIndex < 0) {
                 pointerIndex = 0;
@@ -6207,6 +5940,7 @@
 
             switch (event.getActionMasked()) {
                 case MotionEvent.ACTION_DOWN:
+                    mShadeLog.logMotionEvent(event, "onTouch: down action");
                     startExpandMotion(x, y, false /* startTracking */, mExpandedHeight);
                     mMinExpandHeight = 0.0f;
                     mPanelClosedOnDown = isFullyCollapsed();
@@ -6253,6 +5987,10 @@
                     }
                     break;
                 case MotionEvent.ACTION_MOVE:
+                    if (isFullyCollapsed()) {
+                        // If panel is fully collapsed, reset haptic effect before adding movement.
+                        mHasVibratedOnOpen = false;
+                    }
                     addMovement(event);
                     if (!isFullyCollapsed()) {
                         maybeVibrateOnOpening(true /* openingWithTouch */);
@@ -6291,6 +6029,7 @@
 
                 case MotionEvent.ACTION_UP:
                 case MotionEvent.ACTION_CANCEL:
+                    mShadeLog.logMotionEvent(event, "onTouch: up/cancel action");
                     addMovement(event);
                     endMotionEvent(event, x, y, false /* forceCancel */);
                     // mHeightAnimator is null, there is no remaining frame, ends instrumenting.
@@ -6307,15 +6046,6 @@
         }
     }
 
-    /** Listens for config changes. */
-    public class OnConfigurationChangedListener implements
-            NotificationPanelView.OnConfigurationChangedListener {
-        @Override
-        public void onConfigurationChanged(Configuration newConfig) {
-            loadDimens();
-        }
-    }
-
     static class SplitShadeTransitionAdapter extends Transition {
         private static final String PROP_BOUNDS = "splitShadeTransitionAdapter:bounds";
         private static final String[] TRANSITION_PROPERTIES = { PROP_BOUNDS };
@@ -6365,4 +6095,27 @@
             return TRANSITION_PROPERTIES;
         }
     }
+
+    private final class ShadeAccessibilityDelegate extends AccessibilityDelegate {
+        @Override
+        public void onInitializeAccessibilityNodeInfo(View host,
+                AccessibilityNodeInfo info) {
+            super.onInitializeAccessibilityNodeInfo(host, info);
+            info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD);
+            info.addAction(AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP);
+        }
+
+        @Override
+        public boolean performAccessibilityAction(View host, int action, Bundle args) {
+            if (action
+                    == AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD.getId()
+                    || action
+                    == AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP.getId()) {
+                mStatusBarKeyguardViewManager.showBouncer(true);
+                return true;
+            }
+            return super.performAccessibilityAction(host, action, args);
+        }
+    }
 }
+
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 65bd58d..1e63b2d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -284,7 +284,7 @@
                     return true;
                 }
 
-                if (mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()) {
+                if (mStatusBarKeyguardViewManager.isShowingAlternateAuth()) {
                     // capture all touches if the alt auth bouncer is showing
                     return true;
                 }
@@ -322,7 +322,7 @@
                     handled = !mService.isPulsing();
                 }
 
-                if (mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()) {
+                if (mStatusBarKeyguardViewManager.isShowingAlternateAuth()) {
                     // eat the touch
                     handled = true;
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
index 73c6d50..85b259e 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationsQSContainerController.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2022 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.shade
 
 import android.view.View
diff --git a/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt b/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt
index 084b7dc..bf622c9 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/PulsingGestureListener.kt
@@ -52,6 +52,7 @@
         private val centralSurfaces: CentralSurfaces,
         private val ambientDisplayConfiguration: AmbientDisplayConfiguration,
         private val statusBarStateController: StatusBarStateController,
+        private val shadeLogger: ShadeLogger,
         tunerService: TunerService,
         dumpManager: DumpManager
 ) : GestureDetector.SimpleOnGestureListener(), Dumpable {
@@ -77,18 +78,23 @@
     }
 
     override fun onSingleTapUp(e: MotionEvent): Boolean {
-        if (statusBarStateController.isDozing &&
-                singleTapEnabled &&
-                !dockManager.isDocked &&
-                !falsingManager.isProximityNear &&
-                !falsingManager.isFalseTap(LOW_PENALTY)
-        ) {
-            centralSurfaces.wakeUpIfDozing(
+        val isNotDocked = !dockManager.isDocked
+        shadeLogger.logSingleTapUp(statusBarStateController.isDozing, singleTapEnabled, isNotDocked)
+        if (statusBarStateController.isDozing && singleTapEnabled && isNotDocked) {
+            val proximityIsNotNear = !falsingManager.isProximityNear
+            val isNotAFalseTap = !falsingManager.isFalseTap(LOW_PENALTY)
+            shadeLogger.logSingleTapUpFalsingState(proximityIsNotNear, isNotAFalseTap)
+            if (proximityIsNotNear && isNotAFalseTap) {
+                shadeLogger.d("Single tap handled, requesting centralSurfaces.wakeUpIfDozing")
+                centralSurfaces.wakeUpIfDozing(
                     SystemClock.uptimeMillis(),
                     notificationShadeWindowView,
-                    "PULSING_SINGLE_TAP")
+                    "PULSING_SINGLE_TAP"
+                )
+            }
             return true
         }
+        shadeLogger.d("onSingleTapUp event ignored")
         return false
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
index f389dd9..eaf7fae 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
@@ -224,6 +224,6 @@
     }
 
     private NotificationPanelViewController getNotificationPanelViewController() {
-        return getCentralSurfaces().getPanelController();
+        return getCentralSurfaces().getNotificationPanelViewController();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotifPanelEventsModule.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeEventsModule.java
similarity index 77%
rename from packages/SystemUI/src/com/android/systemui/shade/NotifPanelEventsModule.java
rename to packages/SystemUI/src/com/android/systemui/shade/ShadeEventsModule.java
index 6772384..959c339 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotifPanelEventsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeEventsModule.java
@@ -21,10 +21,9 @@
 import dagger.Binds;
 import dagger.Module;
 
-/** Provides a {@link NotifPanelEvents} in {@link SysUISingleton} scope. */
+/** Provides a {@link ShadeStateEvents} in {@link SysUISingleton} scope. */
 @Module
-public abstract class NotifPanelEventsModule {
+public abstract class ShadeEventsModule {
     @Binds
-    abstract NotifPanelEvents bindPanelEvents(
-            NotificationPanelViewController.PanelEventsEmitter impl);
+    abstract ShadeStateEvents bindShadeEvents(ShadeExpansionStateManager impl);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
index 7bba74a..667392c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
@@ -20,6 +20,7 @@
 import android.util.Log
 import androidx.annotation.FloatRange
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.shade.ShadeStateEvents.ShadeStateEventsListener
 import com.android.systemui.util.Compile
 import java.util.concurrent.CopyOnWriteArrayList
 import javax.inject.Inject
@@ -30,11 +31,12 @@
  * TODO(b/200063118): Make this class the one source of truth for the state of panel expansion.
  */
 @SysUISingleton
-class ShadeExpansionStateManager @Inject constructor() {
+class ShadeExpansionStateManager @Inject constructor() : ShadeStateEvents {
 
     private val expansionListeners = CopyOnWriteArrayList<ShadeExpansionListener>()
     private val qsExpansionListeners = CopyOnWriteArrayList<ShadeQsExpansionListener>()
     private val stateListeners = CopyOnWriteArrayList<ShadeStateListener>()
+    private val shadeStateEventsListeners = CopyOnWriteArrayList<ShadeStateEventsListener>()
 
     @PanelState private var state: Int = STATE_CLOSED
     @FloatRange(from = 0.0, to = 1.0) private var fraction: Float = 0f
@@ -79,6 +81,14 @@
         stateListeners.remove(listener)
     }
 
+    override fun addShadeStateEventsListener(listener: ShadeStateEventsListener) {
+        shadeStateEventsListeners.addIfAbsent(listener)
+    }
+
+    override fun removeShadeStateEventsListener(listener: ShadeStateEventsListener) {
+        shadeStateEventsListeners.remove(listener)
+    }
+
     /** Returns true if the panel is currently closed and false otherwise. */
     fun isClosed(): Boolean = state == STATE_CLOSED
 
@@ -162,6 +172,24 @@
         stateListeners.forEach { it.onPanelStateChanged(state) }
     }
 
+    fun notifyLaunchingActivityChanged(isLaunchingActivity: Boolean) {
+        for (cb in shadeStateEventsListeners) {
+            cb.onLaunchingActivityChanged(isLaunchingActivity)
+        }
+    }
+
+    fun notifyPanelCollapsingChanged(isCollapsing: Boolean) {
+        for (cb in shadeStateEventsListeners) {
+            cb.onPanelCollapsingChanged(isCollapsing)
+        }
+    }
+
+    fun notifyExpandImmediateChange(expandImmediateEnabled: Boolean) {
+        for (cb in shadeStateEventsListeners) {
+            cb.onExpandImmediateChanged(expandImmediateEnabled)
+        }
+    }
+
     private fun debugLog(msg: String) {
         if (!DEBUG) return
         Log.v(TAG, msg)
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
index 2b788d8..40ed40a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeLogger.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2022 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.shade
 
 import android.view.MotionEvent
@@ -16,6 +32,10 @@
         buffer.log(TAG, LogLevel.VERBOSE, msg)
     }
 
+    fun d(@CompileTimeConstant msg: String) {
+        buffer.log(TAG, LogLevel.DEBUG, msg)
+    }
+
     private inline fun log(
         logLevel: LogLevel,
         initializer: LogMessage.() -> Unit,
@@ -77,4 +97,71 @@
             }
         )
     }
+
+    fun logExpansionChanged(
+            message: String,
+            fraction: Float,
+            expanded: Boolean,
+            tracking: Boolean,
+            dragDownPxAmount: Float,
+    ) {
+        log(LogLevel.VERBOSE, {
+            str1 = message
+            double1 = fraction.toDouble()
+            bool1 = expanded
+            bool2 = tracking
+            long1 = dragDownPxAmount.toLong()
+        }, {
+            "$str1 fraction=$double1,expanded=$bool1," +
+                    "tracking=$bool2," + "dragDownPxAmount=$dragDownPxAmount"
+        })
+    }
+
+    fun logQsExpansionChanged(
+            message: String,
+            qsExpanded: Boolean,
+            qsMinExpansionHeight: Int,
+            qsMaxExpansionHeight: Int,
+            stackScrollerOverscrolling: Boolean,
+            dozing: Boolean,
+            qsAnimatorExpand: Boolean,
+            animatingQs: Boolean
+    ) {
+        log(LogLevel.VERBOSE, {
+            str1 = message
+            bool1 = qsExpanded
+            int1 = qsMinExpansionHeight
+            int2 = qsMaxExpansionHeight
+            bool2 = stackScrollerOverscrolling
+            bool3 = dozing
+            bool4 = qsAnimatorExpand
+            // 0 = false, 1 = true
+            long1 = animatingQs.compareTo(false).toLong()
+        }, {
+            "$str1 qsExpanded=$bool1,qsMinExpansionHeight=$int1,qsMaxExpansionHeight=$int2," +
+                    "stackScrollerOverscrolling=$bool2,dozing=$bool3,qsAnimatorExpand=$bool4," +
+                    "animatingQs=$long1"
+        })
+    }
+
+    fun logSingleTapUp(isDozing: Boolean, singleTapEnabled: Boolean, isNotDocked: Boolean) {
+        log(LogLevel.DEBUG, {
+            bool1 = isDozing
+            bool2 = singleTapEnabled
+            bool3 = isNotDocked
+        }, {
+            "PulsingGestureListener#onSingleTapUp all of this must true for single " +
+              "tap to be detected: isDozing: $bool1, singleTapEnabled: $bool2, isNotDocked: $bool3"
+        })
+    }
+
+    fun logSingleTapUpFalsingState(proximityIsNotNear: Boolean, isNotFalseTap: Boolean) {
+        log(LogLevel.DEBUG, {
+            bool1 = proximityIsNotNear
+            bool2 = isNotFalseTap
+        }, {
+            "PulsingGestureListener#onSingleTapUp all of this must true for single " +
+                    "tap to be detected: proximityIsNotNear: $bool1, isNotFalseTap: $bool2"
+        })
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotifPanelEvents.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeStateEvents.kt
similarity index 70%
rename from packages/SystemUI/src/com/android/systemui/shade/NotifPanelEvents.kt
rename to packages/SystemUI/src/com/android/systemui/shade/ShadeStateEvents.kt
index 4558061..56bb1a6 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotifPanelEvents.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeStateEvents.kt
@@ -16,27 +16,25 @@
 
 package com.android.systemui.shade
 
-/** Provides certain notification panel events.  */
-interface NotifPanelEvents {
+/** Provides certain notification panel events. */
+interface ShadeStateEvents {
 
-    /** Registers callbacks to be invoked when notification panel events occur.  */
-    fun registerListener(listener: Listener)
+    /** Registers callbacks to be invoked when notification panel events occur. */
+    fun addShadeStateEventsListener(listener: ShadeStateEventsListener)
 
-    /** Unregisters callbacks previously registered via [registerListener]  */
-    fun unregisterListener(listener: Listener)
+    /** Unregisters callbacks previously registered via [addShadeStateEventsListener] */
+    fun removeShadeStateEventsListener(listener: ShadeStateEventsListener)
 
     /** Callbacks for certain notification panel events. */
-    interface Listener {
+    interface ShadeStateEventsListener {
 
         /** Invoked when the notification panel starts or stops collapsing. */
-        @JvmDefault
-        fun onPanelCollapsingChanged(isCollapsing: Boolean) {}
+        @JvmDefault fun onPanelCollapsingChanged(isCollapsing: Boolean) {}
 
         /**
          * Invoked when the notification panel starts or stops launching an [android.app.Activity].
          */
-        @JvmDefault
-        fun onLaunchingActivityChanged(isLaunchingActivity: Boolean) {}
+        @JvmDefault fun onLaunchingActivityChanged(isLaunchingActivity: Boolean) {}
 
         /**
          * Invoked when the "expand immediate" attribute changes.
@@ -47,7 +45,6 @@
          * Another example is when full QS is showing, and we swipe up from the bottom. Instead of
          * going to QQS, the panel fully collapses.
          */
-        @JvmDefault
-        fun onExpandImmediateChanged(isExpandImmediateEnabled: Boolean) {}
+        @JvmDefault fun onExpandImmediateChanged(isExpandImmediateEnabled: Boolean) {}
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/NoOpOverScroller.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/NoOpOverScroller.kt
index f4db3ab..8847dbd 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/transition/NoOpOverScroller.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/NoOpOverScroller.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2022 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.shade.transition
 
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
index a77c21a..218e897 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/ScrimShadeTransitionController.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2022 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.shade.transition
 
 import android.content.res.Configuration
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeOverScroller.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeOverScroller.kt
index 22e847d..a4642e0 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeOverScroller.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeOverScroller.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2022 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.shade.transition
 
 import com.android.systemui.shade.PanelState
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt
index 1e8208f..1054aa5 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/ShadeTransitionController.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2022 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.shade.transition
 
 import android.content.Context
diff --git a/packages/SystemUI/src/com/android/systemui/shade/transition/SplitShadeOverScroller.kt b/packages/SystemUI/src/com/android/systemui/shade/transition/SplitShadeOverScroller.kt
index 8c57194..fde08ee 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/transition/SplitShadeOverScroller.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/transition/SplitShadeOverScroller.kt
@@ -1,3 +1,19 @@
+/*
+ * Copyright (C) 2022 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.shade.transition
 
 import android.animation.Animator
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
index 4ae0f6a..f786ced 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java
@@ -39,7 +39,7 @@
 import android.hardware.biometrics.IBiometricSysuiReceiver;
 import android.hardware.biometrics.PromptInfo;
 import android.hardware.display.DisplayManager;
-import android.hardware.fingerprint.IUdfpsHbmListener;
+import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
 import android.inputmethodservice.InputMethodService.BackDispositionMode;
 import android.media.INearbyMediaDevicesProvider;
 import android.media.MediaRoute2Info;
@@ -53,7 +53,7 @@
 import android.util.Pair;
 import android.util.SparseArray;
 import android.view.InsetsState.InternalInsetsType;
-import android.view.InsetsVisibilities;
+import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowInsetsController.Behavior;
 
@@ -154,7 +154,7 @@
     //TODO(b/169175022) Update name and when feature name is locked.
     private static final int MSG_EMERGENCY_ACTION_LAUNCH_GESTURE      = 58 << MSG_SHIFT;
     private static final int MSG_SET_NAVIGATION_BAR_LUMA_SAMPLING_ENABLED = 59 << MSG_SHIFT;
-    private static final int MSG_SET_UDFPS_HBM_LISTENER = 60 << MSG_SHIFT;
+    private static final int MSG_SET_UDFPS_REFRESH_RATE_CALLBACK = 60 << MSG_SHIFT;
     private static final int MSG_TILE_SERVICE_REQUEST_ADD = 61 << MSG_SHIFT;
     private static final int MSG_TILE_SERVICE_REQUEST_CANCEL = 62 << MSG_SHIFT;
     private static final int MSG_SET_BIOMETRICS_LISTENER = 63 << MSG_SHIFT;
@@ -333,9 +333,9 @@
         }
 
         /**
-         * @see IStatusBar#setUdfpsHbmListener(IUdfpsHbmListener)
+         * @see IStatusBar#setUdfpsRefreshRateCallback(IUdfpsRefreshRateRequestCallback)
          */
-        default void setUdfpsHbmListener(IUdfpsHbmListener listener) {
+        default void setUdfpsRefreshRateCallback(IUdfpsRefreshRateRequestCallback callback) {
         }
 
         /**
@@ -356,11 +356,11 @@
         default void onRecentsAnimationStateChanged(boolean running) { }
 
         /**
-         * @see IStatusBar#onSystemBarAttributesChanged.
+         * @see IStatusBar#onSystemBarAttributesChanged
          */
         default void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
                 AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
-                @Behavior int behavior, InsetsVisibilities requestedVisibilities,
+                @Behavior int behavior, @InsetsType int requestedVisibleTypes,
                 String packageName, LetterboxDetails[] letterboxDetails) { }
 
         /**
@@ -1017,9 +1017,9 @@
     }
 
     @Override
-    public void setUdfpsHbmListener(IUdfpsHbmListener listener) {
+    public void setUdfpsRefreshRateCallback(IUdfpsRefreshRateRequestCallback callback) {
         synchronized (mLock) {
-            mHandler.obtainMessage(MSG_SET_UDFPS_HBM_LISTENER, listener).sendToTarget();
+            mHandler.obtainMessage(MSG_SET_UDFPS_REFRESH_RATE_CALLBACK, callback).sendToTarget();
         }
     }
 
@@ -1090,7 +1090,7 @@
     @Override
     public void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
             AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
-            @Behavior int behavior, InsetsVisibilities requestedVisibilities, String packageName,
+            @Behavior int behavior, @InsetsType int requestedVisibleTypes, String packageName,
             LetterboxDetails[] letterboxDetails) {
         synchronized (mLock) {
             SomeArgs args = SomeArgs.obtain();
@@ -1099,7 +1099,7 @@
             args.argi3 = navbarColorManagedByIme ? 1 : 0;
             args.arg1 = appearanceRegions;
             args.argi4 = behavior;
-            args.arg2 = requestedVisibilities;
+            args.argi5 = requestedVisibleTypes;
             args.arg3 = packageName;
             args.arg4 = letterboxDetails;
             mHandler.obtainMessage(MSG_SYSTEM_BAR_CHANGED, args).sendToTarget();
@@ -1546,9 +1546,10 @@
                                 (IBiometricContextListener) msg.obj);
                     }
                     break;
-                case MSG_SET_UDFPS_HBM_LISTENER:
+                case MSG_SET_UDFPS_REFRESH_RATE_CALLBACK:
                     for (int i = 0; i < mCallbacks.size(); i++) {
-                        mCallbacks.get(i).setUdfpsHbmListener((IUdfpsHbmListener) msg.obj);
+                        mCallbacks.get(i).setUdfpsRefreshRateCallback(
+                                (IUdfpsRefreshRateRequestCallback) msg.obj);
                     }
                     break;
                 case MSG_SHOW_CHARGING_ANIMATION:
@@ -1581,8 +1582,7 @@
                     for (int i = 0; i < mCallbacks.size(); i++) {
                         mCallbacks.get(i).onSystemBarAttributesChanged(args.argi1, args.argi2,
                                 (AppearanceRegion[]) args.arg1, args.argi3 == 1, args.argi4,
-                                (InsetsVisibilities) args.arg2, (String) args.arg3,
-                                (LetterboxDetails[]) args.arg4);
+                                args.argi5, (String) args.arg3, (LetterboxDetails[]) args.arg4);
                     }
                     args.recycle();
                     break;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index a2e4536..b8302d7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -663,7 +663,7 @@
         } else {
             pulseHeight = height
             val overflow = nsslController.setPulseHeight(height)
-            notificationPanelController.setOverStrechAmount(overflow)
+            notificationPanelController.setOverStretchAmount(overflow)
             val transitionHeight = if (keyguardBypassController.bypassEnabled) height else 0.0f
             transitionToShadeAmountCommon(transitionHeight)
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
index 87ef92a..408293c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationShelf.java
@@ -496,9 +496,6 @@
             return;
         }
 
-        final float smallCornerRadius =
-                getResources().getDimension(R.dimen.notification_corner_radius_small)
-                /  getResources().getDimension(R.dimen.notification_corner_radius);
         final float viewEnd = viewStart + anv.getActualHeight();
         final float cornerAnimationDistance = mCornerAnimationDistance
                 * mAmbientState.getExpansionFraction();
@@ -509,7 +506,7 @@
             final float changeFraction = MathUtils.saturate(
                     (viewEnd - cornerAnimationTop) / cornerAnimationDistance);
             anv.requestBottomRoundness(
-                    anv.isLastInSection() ? 1f : changeFraction,
+                    /* value = */ anv.isLastInSection() ? 1f : changeFraction,
                     /* animate = */ false,
                     SourceType.OnScroll);
 
@@ -517,7 +514,7 @@
             // Fast scroll skips frames and leaves corners with unfinished rounding.
             // Reset top and bottom corners outside of animation bounds.
             anv.requestBottomRoundness(
-                    anv.isLastInSection() ? 1f : smallCornerRadius,
+                    /* value = */ anv.isLastInSection() ? 1f : 0f,
                     /* animate = */ false,
                     SourceType.OnScroll);
         }
@@ -527,16 +524,16 @@
             final float changeFraction = MathUtils.saturate(
                     (viewStart - cornerAnimationTop) / cornerAnimationDistance);
             anv.requestTopRoundness(
-                    anv.isFirstInSection() ? 1f : changeFraction,
-                    false,
+                    /* value = */ anv.isFirstInSection() ? 1f : changeFraction,
+                    /* animate = */ false,
                     SourceType.OnScroll);
 
         } else if (viewStart < cornerAnimationTop) {
             // Fast scroll skips frames and leaves corners with unfinished rounding.
             // Reset top and bottom corners outside of animation bounds.
             anv.requestTopRoundness(
-                    anv.isFirstInSection() ? 1f : smallCornerRadius,
-                    false,
+                    /* value = */ anv.isFirstInSection() ? 1f : 0f,
+                    /* animate = */ false,
                     SourceType.OnScroll);
         }
     }
@@ -976,6 +973,16 @@
         mIndexOfFirstViewInShelf = mHostLayoutController.indexOfChild(firstViewInShelf);
     }
 
+    /**
+     * This method resets the OnScroll roundness of a view to 0f
+     *
+     * Note: This should be the only class that handles roundness {@code SourceType.OnScroll}
+     */
+    public static void resetOnScrollRoundness(ExpandableView expandableView) {
+        expandableView.requestTopRoundness(0f, false, SourceType.OnScroll);
+        expandableView.requestBottomRoundness(0f, false, SourceType.OnScroll);
+    }
+
     public class ShelfState extends ExpandableViewState {
         private boolean hasItemsInStableShelf;
         private ExpandableView firstViewInShelf;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
index c04bc82..fdec745 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/StatusBarStateControllerImpl.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.statusbar;
 
-import static android.view.InsetsState.ITYPE_NAVIGATION_BAR;
-import static android.view.InsetsState.ITYPE_STATUS_BAR;
 import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
 
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_TRANSITION_FROM_AOD;
@@ -34,9 +32,10 @@
 import android.util.Log;
 import android.view.Choreographer;
 import android.view.InsetsFlags;
-import android.view.InsetsVisibilities;
 import android.view.View;
 import android.view.ViewDebug;
+import android.view.WindowInsets;
+import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowInsetsController.Behavior;
 import android.view.animation.Interpolator;
@@ -497,9 +496,9 @@
 
     @Override
     public void setSystemBarAttributes(@Appearance int appearance, @Behavior int behavior,
-            InsetsVisibilities requestedVisibilities, String packageName) {
-        boolean isFullscreen = !requestedVisibilities.getVisibility(ITYPE_STATUS_BAR)
-                || !requestedVisibilities.getVisibility(ITYPE_NAVIGATION_BAR);
+            @InsetsType int requestedVisibleTypes, String packageName) {
+        boolean isFullscreen = (requestedVisibleTypes & WindowInsets.Type.statusBars()) == 0
+                || (requestedVisibleTypes & WindowInsets.Type.navigationBars()) == 0;
         if (mIsFullscreen != isFullscreen) {
             mIsFullscreen = isFullscreen;
             synchronized (mListeners) {
@@ -514,12 +513,12 @@
         if (DEBUG_IMMERSIVE_APPS) {
             boolean dim = (appearance & APPEARANCE_LOW_PROFILE_BARS) != 0;
             String behaviorName = ViewDebug.flagsToString(InsetsFlags.class, "behavior", behavior);
-            String requestedVisibilityString = requestedVisibilities.toString();
-            if (requestedVisibilityString.isEmpty()) {
-                requestedVisibilityString = "none";
+            String requestedVisibleTypesString = WindowInsets.Type.toString(requestedVisibleTypes);
+            if (requestedVisibleTypesString.isEmpty()) {
+                requestedVisibleTypesString = "none";
             }
             Log.d(TAG, packageName + " dim=" + dim + " behavior=" + behaviorName
-                    + " requested visibilities: " + requestedVisibilityString);
+                    + " requested visible types: " + requestedVisibleTypesString);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
index 2cc7738..1189107 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/SysuiStatusBarStateController.java
@@ -19,8 +19,8 @@
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
 import android.annotation.IntDef;
-import android.view.InsetsVisibilities;
 import android.view.View;
+import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowInsetsController.Behavior;
 
@@ -154,7 +154,7 @@
      * Set the system bar attributes
      */
     void setSystemBarAttributes(@Appearance int appearance, @Behavior int behavior,
-            InsetsVisibilities requestedVisibilities, String packageName);
+            @InsetsType int requestedVisibleTypes, String packageName);
 
     /**
      * Set pulsing
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
index fc984618..5873837 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/lockscreen/LockscreenSmartspaceController.kt
@@ -48,7 +48,8 @@
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.settings.UserTracker
-import com.android.systemui.shared.regionsampling.RegionSamplingInstance
+import com.android.systemui.shared.regionsampling.RegionSampler
+import com.android.systemui.shared.regionsampling.UpdateColorCallback
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
@@ -90,8 +91,8 @@
 
     // Smartspace can be used on multiple displays, such as when the user casts their screen
     private var smartspaceViews = mutableSetOf<SmartspaceView>()
-    private var regionSamplingInstances =
-            mutableMapOf<SmartspaceView, RegionSamplingInstance>()
+    private var regionSamplers =
+            mutableMapOf<SmartspaceView, RegionSampler>()
 
     private val regionSamplingEnabled =
             featureFlags.isEnabled(Flags.REGION_SAMPLING)
@@ -101,27 +102,23 @@
     private var showSensitiveContentForManagedUser = false
     private var managedUserHandle: UserHandle? = null
 
-    private val updateFun = object : RegionSamplingInstance.UpdateColorCallback {
-        override fun updateColors() {
-            updateTextColorFromRegionSampler()
-        }
-    }
+    private val updateFun: UpdateColorCallback = { updateTextColorFromRegionSampler() }
 
     // TODO: Move logic into SmartspaceView
     var stateChangeListener = object : View.OnAttachStateChangeListener {
         override fun onViewAttachedToWindow(v: View) {
             smartspaceViews.add(v as SmartspaceView)
 
-            var regionSamplingInstance = RegionSamplingInstance(
+            var regionSampler = RegionSampler(
                     v,
                     uiExecutor,
                     bgExecutor,
                     regionSamplingEnabled,
                     updateFun
             )
-            initializeTextColors(regionSamplingInstance)
-            regionSamplingInstance.startRegionSampler()
-            regionSamplingInstances.put(v, regionSamplingInstance)
+            initializeTextColors(regionSampler)
+            regionSampler.startRegionSampler()
+            regionSamplers.put(v, regionSampler)
             connectSession()
 
             updateTextColorFromWallpaper()
@@ -131,9 +128,9 @@
         override fun onViewDetachedFromWindow(v: View) {
             smartspaceViews.remove(v as SmartspaceView)
 
-            var regionSamplingInstance = regionSamplingInstances.getValue(v)
-            regionSamplingInstance.stopRegionSampler()
-            regionSamplingInstances.remove(v)
+            var regionSampler = regionSamplers.getValue(v)
+            regionSampler.stopRegionSampler()
+            regionSamplers.remove(v)
 
             if (smartspaceViews.isEmpty()) {
                 disconnect()
@@ -363,19 +360,19 @@
         }
     }
 
-    private fun initializeTextColors(regionSamplingInstance: RegionSamplingInstance) {
+    private fun initializeTextColors(regionSampler: RegionSampler) {
         val lightThemeContext = ContextThemeWrapper(context, R.style.Theme_SystemUI_LightWallpaper)
         val darkColor = Utils.getColorAttrDefaultColor(lightThemeContext, R.attr.wallpaperTextColor)
 
         val darkThemeContext = ContextThemeWrapper(context, R.style.Theme_SystemUI)
         val lightColor = Utils.getColorAttrDefaultColor(darkThemeContext, R.attr.wallpaperTextColor)
 
-        regionSamplingInstance.setForegroundColors(lightColor, darkColor)
+        regionSampler.setForegroundColors(lightColor, darkColor)
     }
 
     private fun updateTextColorFromRegionSampler() {
         smartspaceViews.forEach {
-            val textColor = regionSamplingInstances.getValue(it).currentForegroundColor()
+            val textColor = regionSamplers.getValue(it).currentForegroundColor()
             it.setPrimaryTextColor(textColor)
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
index 8a31ed9..5dbb4f9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
@@ -33,6 +33,7 @@
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender.OnEndLifetimeExtensionCallback
+import com.android.systemui.statusbar.notification.collection.provider.LaunchFullScreenIntentProvider
 import com.android.systemui.statusbar.notification.collection.render.NodeController
 import com.android.systemui.statusbar.notification.dagger.IncomingHeader
 import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
@@ -68,6 +69,7 @@
     private val mHeadsUpViewBinder: HeadsUpViewBinder,
     private val mNotificationInterruptStateProvider: NotificationInterruptStateProvider,
     private val mRemoteInputManager: NotificationRemoteInputManager,
+    private val mLaunchFullScreenIntentProvider: LaunchFullScreenIntentProvider,
     @IncomingHeader private val mIncomingHeaderController: NodeController,
     @Main private val mExecutor: DelayableExecutor,
 ) : Coordinator {
@@ -198,6 +200,13 @@
             // At this point we just need to initiate the transfer
             val summaryUpdate = mPostedEntries[logicalSummary.key]
 
+            // Because we now know for certain that some child is going to alert for this summary
+            // (as we have found a child to transfer the alert to), mark the group as having
+            // interrupted. This will allow us to know in the future that the "should heads up"
+            // state of this group has already been handled, just not via the summary entry itself.
+            logicalSummary.setInterruption()
+            mLogger.logSummaryMarkedInterrupted(logicalSummary.key, childToReceiveParentAlert.key)
+
             // If the summary was not attached, then remove the alert from the detached summary.
             // Otherwise we can simply ignore its posted update.
             if (!isSummaryAttached) {
@@ -373,6 +382,12 @@
          * Notification was just added and if it should heads up, bind the view and then show it.
          */
         override fun onEntryAdded(entry: NotificationEntry) {
+            // First check whether this notification should launch a full screen intent, and
+            // launch it if needed.
+            if (mNotificationInterruptStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry)) {
+                mLaunchFullScreenIntentProvider.launchFullScreenIntent(entry)
+            }
+
             // shouldHeadsUp includes check for whether this notification should be filtered
             val shouldHeadsUpEver = mNotificationInterruptStateProvider.shouldHeadsUp(entry)
             mPostedEntries[entry.key] = PostedEntry(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
index dfaa291..473c35d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorLogger.kt
@@ -69,4 +69,13 @@
             "updating entry via ranking applied: $str1 updated shouldHeadsUp=$bool1"
         })
     }
+
+    fun logSummaryMarkedInterrupted(summaryKey: String, childKey: String) {
+        buffer.log(TAG, LogLevel.DEBUG, {
+            str1 = summaryKey
+            str2 = childKey
+        }, {
+            "marked group summary as interrupted: $str1 for alert transfer to child: $str2"
+        })
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
index d3bc257..3002a68 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinator.java
@@ -28,7 +28,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shade.NotifPanelEvents;
+import com.android.systemui.shade.ShadeStateEvents;
 import com.android.systemui.statusbar.notification.collection.GroupEntry;
 import com.android.systemui.statusbar.notification.collection.ListEntry;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -55,12 +55,12 @@
 // TODO(b/204468557): Move to @CoordinatorScope
 @SysUISingleton
 public class VisualStabilityCoordinator implements Coordinator, Dumpable,
-        NotifPanelEvents.Listener {
+        ShadeStateEvents.ShadeStateEventsListener {
     public static final String TAG = "VisualStability";
     public static final boolean DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE);
     private final DelayableExecutor mDelayableExecutor;
     private final HeadsUpManager mHeadsUpManager;
-    private final NotifPanelEvents mNotifPanelEvents;
+    private final ShadeStateEvents mShadeStateEvents;
     private final StatusBarStateController mStatusBarStateController;
     private final VisualStabilityProvider mVisualStabilityProvider;
     private final WakefulnessLifecycle mWakefulnessLifecycle;
@@ -92,7 +92,7 @@
             DelayableExecutor delayableExecutor,
             DumpManager dumpManager,
             HeadsUpManager headsUpManager,
-            NotifPanelEvents notifPanelEvents,
+            ShadeStateEvents shadeStateEvents,
             StatusBarStateController statusBarStateController,
             VisualStabilityProvider visualStabilityProvider,
             WakefulnessLifecycle wakefulnessLifecycle) {
@@ -101,7 +101,7 @@
         mWakefulnessLifecycle = wakefulnessLifecycle;
         mStatusBarStateController = statusBarStateController;
         mDelayableExecutor = delayableExecutor;
-        mNotifPanelEvents = notifPanelEvents;
+        mShadeStateEvents = shadeStateEvents;
 
         dumpManager.registerDumpable(this);
     }
@@ -114,7 +114,7 @@
 
         mStatusBarStateController.addCallback(mStatusBarStateControllerListener);
         mPulsing = mStatusBarStateController.isPulsing();
-        mNotifPanelEvents.registerListener(this);
+        mShadeStateEvents.addShadeStateEventsListener(this);
 
         pipeline.setVisualStabilityManager(mNotifStabilityManager);
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/LaunchFullScreenIntentProvider.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/LaunchFullScreenIntentProvider.kt
new file mode 100644
index 0000000..74ff78e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/provider/LaunchFullScreenIntentProvider.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.notification.collection.provider
+
+import android.util.Log
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.util.ListenerSet
+import javax.inject.Inject
+
+/**
+ * A class that enables communication of decisions to launch a notification's full screen intent.
+ */
+@SysUISingleton
+class LaunchFullScreenIntentProvider @Inject constructor() {
+    companion object {
+        private const val TAG = "LaunchFullScreenIntentProvider"
+    }
+    private val listeners = ListenerSet<Listener>()
+
+    /**
+     * Registers a listener with this provider. These listeners will be alerted whenever a full
+     * screen intent should be launched for a notification entry.
+     */
+    fun registerListener(listener: Listener) {
+        listeners.addIfAbsent(listener)
+    }
+
+    /** Removes the specified listener. */
+    fun removeListener(listener: Listener) {
+        listeners.remove(listener)
+    }
+
+    /**
+     * Sends a request to launch full screen intent for the given notification entry to all
+     * registered listeners.
+     */
+    fun launchFullScreenIntent(entry: NotificationEntry) {
+        if (listeners.isEmpty()) {
+            // This should never happen, but we should definitely know if it does because having
+            // no listeners would indicate that FSIs are getting entirely dropped on the floor.
+            Log.wtf(TAG, "no listeners found when launchFullScreenIntent requested")
+        }
+        for (listener in listeners) {
+            listener.onFullScreenIntentRequested(entry)
+        }
+    }
+
+    /** Listener interface for passing full screen intent launch decisions. */
+    fun interface Listener {
+        /**
+         * Invoked whenever a full screen intent launch is requested for the given notification
+         * entry.
+         */
+        fun onFullScreenIntentRequested(entry: NotificationEntry)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
index da4cced..ff63891 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/dagger/NotificationsModule.java
@@ -32,8 +32,8 @@
 import com.android.systemui.people.widget.PeopleSpaceWidgetManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.settings.UserContextProvider;
-import com.android.systemui.shade.NotifPanelEventsModule;
 import com.android.systemui.shade.ShadeController;
+import com.android.systemui.shade.ShadeEventsModule;
 import com.android.systemui.statusbar.NotificationListener;
 import com.android.systemui.statusbar.notification.AssistantFeedbackController;
 import com.android.systemui.statusbar.notification.collection.NotifInflaterImpl;
@@ -93,7 +93,7 @@
 @Module(includes = {
         CoordinatorsModule.class,
         KeyguardNotificationVisibilityProviderModule.class,
-        NotifPanelEventsModule.class,
+        ShadeEventsModule.class,
         NotifPipelineChoreographerModule.class,
         NotificationSectionHeadersModule.class,
 })
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index 26f0ad9..0554fb5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -44,6 +44,7 @@
 import com.android.systemui.R;
 import com.android.systemui.statusbar.CrossFadeHelper;
 import com.android.systemui.statusbar.NotificationGroupingUtil;
+import com.android.systemui.statusbar.NotificationShelf;
 import com.android.systemui.statusbar.notification.FeedbackIcon;
 import com.android.systemui.statusbar.notification.NotificationFadeAware;
 import com.android.systemui.statusbar.notification.NotificationUtils;
@@ -308,6 +309,11 @@
 
         row.setContentTransformationAmount(0, false /* isLastChild */);
         row.setNotificationFaded(mContainingNotificationIsFaded);
+
+        // This is a workaround, the NotificationShelf should be the owner of `OnScroll` roundness.
+        // Here we should reset the `OnScroll` roundness only on top-level rows.
+        NotificationShelf.resetOnScrollRoundness(row);
+
         // It doesn't make sense to keep old animations around, lets cancel them!
         ExpandableViewState viewState = row.getViewState();
         if (viewState != null) {
@@ -1377,8 +1383,12 @@
             if (child.getVisibility() == View.GONE) {
                 continue;
             }
+            child.requestTopRoundness(
+                    /* value = */ 0f,
+                    /* animate = */ isShown(),
+                    SourceType.DefaultValue);
             child.requestBottomRoundness(
-                    last ? getBottomRoundness() : 0f,
+                    /* value = */ last ? getBottomRoundness() : 0f,
                     /* animate = */ isShown(),
                     SourceType.DefaultValue);
             last = false;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index df705c5..2c3330e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -114,6 +114,8 @@
 import com.android.systemui.util.DumpUtilsKt;
 import com.android.systemui.util.LargeScreenUtils;
 
+import com.google.errorprone.annotations.CompileTimeConstant;
+
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.util.ArrayList;
@@ -1383,7 +1385,7 @@
             if (height < minExpansionHeight) {
                 mClipRect.left = 0;
                 mClipRect.right = getWidth();
-                mClipRect.top = 0;
+                mClipRect.top = getNotificationsClippingTopBound();
                 mClipRect.bottom = (int) height;
                 height = minExpansionHeight;
                 setRequestedClipBounds(mClipRect);
@@ -1444,6 +1446,17 @@
         notifyAppearChangedListeners();
     }
 
+    private int getNotificationsClippingTopBound() {
+        if (isHeadsUpTransition()) {
+            // HUN in split shade can go higher than bottom of NSSL when swiping up so we want
+            // to give it extra clipping margin. Because clipping has rounded corners, we also
+            // need to account for that corner clipping.
+            return -mAmbientState.getStackTopMargin() - mCornerRadius;
+        } else {
+            return 0;
+        }
+    }
+
     private void notifyAppearChangedListeners() {
         float appear;
         float expandAmount;
@@ -1482,7 +1495,6 @@
     public void updateClipping() {
         boolean clipped = mRequestedClipBounds != null && !mInHeadsUpPinnedMode
                 && !mHeadsUpAnimatingAway;
-        boolean clipToOutline = false;
         if (mIsClipped != clipped) {
             mIsClipped = clipped;
         }
@@ -1498,7 +1510,7 @@
             setClipBounds(null);
         }
 
-        setClipToOutline(clipToOutline);
+        setClipToOutline(false);
     }
 
     /**
@@ -3683,6 +3695,8 @@
 
     @ShadeViewRefactor(RefactorComponent.INPUT)
     void handleEmptySpaceClick(MotionEvent ev) {
+        logEmptySpaceClick(ev, isBelowLastNotification(mInitialTouchX, mInitialTouchY),
+                mStatusBarState, mTouchIsClick);
         switch (ev.getActionMasked()) {
             case MotionEvent.ACTION_MOVE:
                 final float touchSlop = getTouchSlop(ev);
@@ -3694,12 +3708,34 @@
             case MotionEvent.ACTION_UP:
                 if (mStatusBarState != StatusBarState.KEYGUARD && mTouchIsClick &&
                         isBelowLastNotification(mInitialTouchX, mInitialTouchY)) {
+                    debugLog("handleEmptySpaceClick: touch event propagated further");
                     mOnEmptySpaceClickListener.onEmptySpaceClicked(mInitialTouchX, mInitialTouchY);
                 }
                 break;
+            default:
+                debugLog("handleEmptySpaceClick: MotionEvent ignored");
         }
     }
 
+    private void debugLog(@CompileTimeConstant String s) {
+        if (mLogger == null) {
+            return;
+        }
+        mLogger.d(s);
+    }
+
+    private void logEmptySpaceClick(MotionEvent ev, boolean isTouchBelowLastNotification,
+            int statusBarState, boolean touchIsClick) {
+        if (mLogger == null) {
+            return;
+        }
+        mLogger.logEmptySpaceClick(
+                isTouchBelowLastNotification,
+                statusBarState,
+                touchIsClick,
+                MotionEvent.actionToString(ev.getActionMasked()));
+    }
+
     @ShadeViewRefactor(RefactorComponent.INPUT)
     void initDownStates(MotionEvent ev) {
         if (ev.getAction() == MotionEvent.ACTION_DOWN) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
index 4c52db7..64dd6dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLogger.kt
@@ -2,6 +2,7 @@
 
 import com.android.systemui.log.dagger.NotificationHeadsUpLog
 import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel.DEBUG
 import com.android.systemui.plugins.log.LogLevel.INFO
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.logKey
@@ -10,6 +11,7 @@
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_DISAPPEAR_CLICK
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout.AnimationEvent.ANIMATION_TYPE_HEADS_UP_OTHER
+import com.google.errorprone.annotations.CompileTimeConstant
 import javax.inject.Inject
 
 class NotificationStackScrollLogger @Inject constructor(
@@ -56,6 +58,25 @@
                     "key: $str1 expected: $bool1 actual: $bool2"
         })
     }
+
+    fun d(@CompileTimeConstant msg: String) = buffer.log(TAG, DEBUG, msg)
+
+    fun logEmptySpaceClick(
+        isBelowLastNotification: Boolean,
+        statusBarState: Int,
+        touchIsClick: Boolean,
+        motionEventDesc: String
+    ) {
+        buffer.log(TAG, DEBUG, {
+            int1 = statusBarState
+            bool1 = touchIsClick
+            bool2 = isBelowLastNotification
+            str1 = motionEventDesc
+        }, {
+            "handleEmptySpaceClick: statusBarState: $int1 isTouchAClick: $bool1 " +
+                    "isTouchBelowNotification: $bool2 motionEvent: $str1"
+        })
+    }
 }
 
 private const val TAG = "NotificationStackScroll"
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 9900e41..2f7d344 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -64,8 +64,10 @@
 import java.io.PrintWriter;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.HashSet;
 import java.util.Map;
 import java.util.Optional;
+import java.util.Set;
 
 import javax.inject.Inject;
 
@@ -163,7 +165,7 @@
     private PendingAuthenticated mPendingAuthenticated = null;
     private boolean mHasScreenTurnedOnSinceAuthenticating;
     private boolean mFadedAwayAfterWakeAndUnlock;
-    private BiometricModeListener mBiometricModeListener;
+    private Set<BiometricModeListener> mBiometricModeListeners = new HashSet<>();
 
     private final MetricsLogger mMetricsLogger;
     private final AuthController mAuthController;
@@ -305,9 +307,14 @@
         mKeyguardViewController = keyguardViewController;
     }
 
-    /** Sets a {@link BiometricModeListener}. */
-    public void setBiometricModeListener(BiometricModeListener biometricModeListener) {
-        mBiometricModeListener = biometricModeListener;
+    /** Adds a {@link BiometricModeListener}. */
+    public void addBiometricModeListener(BiometricModeListener listener) {
+        mBiometricModeListeners.add(listener);
+    }
+
+    /** Removes a {@link BiometricModeListener}. */
+    public void removeBiometricModeListener(BiometricModeListener listener) {
+        mBiometricModeListeners.remove(listener);
     }
 
     private final Runnable mReleaseBiometricWakeLockRunnable = new Runnable() {
@@ -481,15 +488,12 @@
                 break;
         }
         onModeChanged(mMode);
-        if (mBiometricModeListener != null) {
-            mBiometricModeListener.notifyBiometricAuthModeChanged();
-        }
         Trace.endSection();
     }
 
     private void onModeChanged(@WakeAndUnlockMode int mode) {
-        if (mBiometricModeListener != null) {
-            mBiometricModeListener.onModeChanged(mode);
+        for (BiometricModeListener listener : mBiometricModeListeners) {
+            listener.onModeChanged(mode);
         }
     }
 
@@ -696,9 +700,8 @@
         mMode = MODE_NONE;
         mBiometricType = null;
         mNotificationShadeWindowController.setForceDozeBrightness(false);
-        if (mBiometricModeListener != null) {
-            mBiometricModeListener.onResetMode();
-            mBiometricModeListener.notifyBiometricAuthModeChanged();
+        for (BiometricModeListener listener : mBiometricModeListeners) {
+            listener.onResetMode();
         }
         mNumConsecutiveFpFailures = 0;
         mLastFpFailureUptimeMillis = 0;
@@ -807,10 +810,8 @@
     /** An interface to interact with the {@link BiometricUnlockController}. */
     public interface BiometricModeListener {
         /** Called when {@code mMode} is reset to {@link #MODE_NONE}. */
-        void onResetMode();
+        default void onResetMode() {}
         /** Called when {@code mMode} has changed in {@link #startWakeAndUnlock(int)}. */
-        void onModeChanged(@WakeAndUnlockMode int mode);
-        /** Called after processing {@link #onModeChanged(int)}. */
-        void notifyBiometricAuthModeChanged();
+        default void onModeChanged(@WakeAndUnlockMode int mode) {}
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index 70cf56d..1961e1d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -41,7 +41,6 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.statusbar.RegisterStatusBarResult;
 import com.android.keyguard.AuthKeyguardMessageArea;
-import com.android.keyguard.FaceAuthApiRequestReason;
 import com.android.systemui.Dumpable;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.animation.RemoteTransitionAdapter;
@@ -197,8 +196,6 @@
 
     void collapsePanelOnMainThread();
 
-    void collapsePanelWithDuration(int duration);
-
     void togglePanel();
 
     void start();
@@ -230,13 +227,6 @@
 
     boolean isShadeDisabled();
 
-    /**
-     * Request face auth to initiated
-     * @param userInitiatedRequest Whether this was a user initiated request
-     * @param reason Reason why face auth was triggered.
-     */
-    void requestFaceAuth(boolean userInitiatedRequest, @FaceAuthApiRequestReason String reason);
-
     @Override
     void startActivity(Intent intent, boolean onlyProvisioned, boolean dismissShade,
             int flags);
@@ -313,9 +303,6 @@
 
     void checkBarModes();
 
-    // Called by NavigationBarFragment
-    void setQsScrimEnabled(boolean scrimEnabled);
-
     void updateBubblesVisibility();
 
     void setInteracting(int barWindow, boolean interacting);
@@ -387,8 +374,6 @@
 
     void showKeyguardImpl();
 
-    boolean isInLaunchTransition();
-
     void fadeKeyguardAfterLaunchTransition(Runnable beforeFading,
             Runnable endRunnable, Runnable cancelRunnable);
 
@@ -445,8 +430,6 @@
 
     void showPinningEscapeToast();
 
-    KeyguardBottomAreaView getKeyguardBottomAreaView();
-
     void setBouncerShowing(boolean bouncerShowing);
 
     void setBouncerShowingOverDream(boolean bouncerShowingOverDream);
@@ -513,12 +496,8 @@
 
     boolean isBouncerShowingOverDream();
 
-    void onBouncerPreHideAnimation();
-
     boolean isKeyguardSecure();
 
-    NotificationPanelViewController getPanelController();
-
     NotificationGutsManager getGutsManager();
 
     void updateNotificationPanelTouchState();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
index d6fadca..41f0520 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacks.java
@@ -38,8 +38,8 @@
 import android.util.Log;
 import android.util.Slog;
 import android.view.InsetsState.InternalInsetsType;
-import android.view.InsetsVisibilities;
 import android.view.KeyEvent;
+import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowInsetsController.Behavior;
 
@@ -458,7 +458,7 @@
     @Override
     public void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
             AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
-            @Behavior int behavior, InsetsVisibilities requestedVisibilities, String packageName,
+            @Behavior int behavior, @InsetsType int requestedVisibleTypes, String packageName,
             LetterboxDetails[] letterboxDetails) {
         if (displayId != mDisplayId) {
             return;
@@ -471,7 +471,7 @@
                 appearanceRegions,
                 navbarColorManagedByIme,
                 behavior,
-                requestedVisibilities,
+                requestedVisibleTypes,
                 packageName,
                 letterboxDetails
         );
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 43bc99f..6bb5272 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -122,7 +122,6 @@
 import com.android.internal.statusbar.IStatusBarService;
 import com.android.internal.statusbar.RegisterStatusBarResult;
 import com.android.keyguard.AuthKeyguardMessageArea;
-import com.android.keyguard.FaceAuthApiRequestReason;
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.keyguard.KeyguardUpdateMonitorCallback;
 import com.android.keyguard.ViewMediatorCallback;
@@ -467,7 +466,7 @@
     private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
     private final CentralSurfacesComponent.Factory mCentralSurfacesComponentFactory;
     private final PluginManager mPluginManager;
-    private final com.android.systemui.shade.ShadeController mShadeController;
+    private final ShadeController mShadeController;
     private final InitController mInitController;
 
     private final PluginDependencyProvider mPluginDependencyProvider;
@@ -480,9 +479,9 @@
     private final StatusBarSignalPolicy mStatusBarSignalPolicy;
     private final StatusBarHideIconsForBouncerManager mStatusBarHideIconsForBouncerManager;
 
-    // expanded notifications
-    // the sliding/resizing panel within the notification window
-    protected NotificationPanelViewController mNotificationPanelViewController;
+    /** Controller for the Shade. */
+    @VisibleForTesting
+    NotificationPanelViewController mNotificationPanelViewController;
 
     // settings
     private QSPanelController mQSPanelController;
@@ -929,7 +928,7 @@
         }
         mCommandQueueCallbacks.onSystemBarAttributesChanged(mDisplayId, result.mAppearance,
                 result.mAppearanceRegions, result.mNavbarColorManagedByIme, result.mBehavior,
-                result.mRequestedVisibilities, result.mPackageName, result.mLetterboxDetails);
+                result.mRequestedVisibleTypes, result.mPackageName, result.mLetterboxDetails);
 
         // StatusBarManagerService has a back up of IME token and it's restored here.
         mCommandQueueCallbacks.setImeWindowStatus(mDisplayId, result.mImeToken,
@@ -1153,7 +1152,6 @@
         initializer.initializeStatusBar(mCentralSurfacesComponent);
 
         mStatusBarTouchableRegionManager.setup(this, mNotificationShadeWindowView);
-        mHeadsUpManager.addListener(mNotificationPanelViewController.getOnHeadsUpChangedListener());
         mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager);
 
         createNavigationBar(result);
@@ -1162,9 +1160,6 @@
             mLockscreenWallpaper = mLockscreenWallpaperLazy.get();
         }
 
-        mNotificationPanelViewController.setKeyguardIndicationController(
-                mKeyguardIndicationController);
-
         mAmbientIndicationContainer = mNotificationShadeWindowView.findViewById(
                 R.id.ambient_indication_container);
 
@@ -1515,11 +1510,12 @@
     protected void startKeyguard() {
         Trace.beginSection("CentralSurfaces#startKeyguard");
         mBiometricUnlockController = mBiometricUnlockControllerLazy.get();
-        mBiometricUnlockController.setBiometricModeListener(
+        mBiometricUnlockController.addBiometricModeListener(
                 new BiometricUnlockController.BiometricModeListener() {
                     @Override
                     public void onResetMode() {
                         setWakeAndUnlocking(false);
+                        notifyBiometricAuthModeChanged();
                     }
 
                     @Override
@@ -1530,11 +1526,7 @@
                             case BiometricUnlockController.MODE_WAKE_AND_UNLOCK:
                                 setWakeAndUnlocking(true);
                         }
-                    }
-
-                    @Override
-                    public void notifyBiometricAuthModeChanged() {
-                        CentralSurfacesImpl.this.notifyBiometricAuthModeChanged();
+                        notifyBiometricAuthModeChanged();
                     }
 
                     private void setWakeAndUnlocking(boolean wakeAndUnlocking) {
@@ -1614,18 +1606,6 @@
         return (mDisabled2 & StatusBarManager.DISABLE2_NOTIFICATION_SHADE) != 0;
     }
 
-    /**
-     * Asks {@link KeyguardUpdateMonitor} to run face auth.
-     */
-    @Override
-    public void requestFaceAuth(boolean userInitiatedRequest,
-            @FaceAuthApiRequestReason String reason) {
-        if (!mKeyguardStateController.canDismissLockScreen()) {
-            mKeyguardUpdateMonitor.requestFaceAuth(
-                    userInitiatedRequest, reason);
-        }
-    }
-
     private void updateReportRejectedTouchVisibility() {
         if (mReportRejectedTouch == null) {
             return;
@@ -2014,8 +1994,7 @@
     }
 
     void makeExpandedInvisible() {
-        if (SPEW) Log.d(TAG, "makeExpandedInvisible: mExpandedVisible=" + mExpandedVisible
-                + " mExpandedVisible=" + mExpandedVisible);
+        if (SPEW) Log.d(TAG, "makeExpandedInvisible: mExpandedVisible=" + mExpandedVisible);
 
         if (!mExpandedVisible || mNotificationShadeWindowView == null) {
             return;
@@ -2191,12 +2170,6 @@
         mNoAnimationOnNextBarModeChange = false;
     }
 
-    // Called by NavigationBarFragment
-    @Override
-    public void setQsScrimEnabled(boolean scrimEnabled) {
-        mNotificationPanelViewController.setQsScrimEnabled(scrimEnabled);
-    }
-
     /** Temporarily hides Bubbles if the status bar is hidden. */
     @Override
     public void updateBubblesVisibility() {
@@ -2572,14 +2545,11 @@
                                 CommandQueue.FLAG_EXCLUDE_RECENTS_PANEL,
                                 true /* force */, true /* delayed*/);
                     } else {
-
                         // Do it after DismissAction has been processed to conserve the needed
                         // ordering.
                         mMainExecutor.execute(mShadeController::runPostCollapseRunnables);
                     }
-                } else if (CentralSurfacesImpl.this.isInLaunchTransition()
-                        && mNotificationPanelViewController.isLaunchTransitionFinished()) {
-
+                } else if (mNotificationPanelViewController.isLaunchTransitionFinished()) {
                     // We are not dismissing the shade, but the launch transition is already
                     // finished,
                     // so nobody will call readyForKeyguardDone anymore. Post it such that
@@ -3001,11 +2971,6 @@
         mPresenter.updateMediaMetaData(true /* metaDataChanged */, true);
     }
 
-    @Override
-    public boolean isInLaunchTransition() {
-        return mNotificationPanelViewController.isLaunchTransitionFinished();
-    }
-
     /**
      * Fades the content of the keyguard away after the launch transition is done.
      *
@@ -3386,12 +3351,6 @@
         }
     }
 
-    /** Collapse the panel. The collapsing will be animated for the given {@code duration}. */
-    @Override
-    public void collapsePanelWithDuration(int duration) {
-        mNotificationPanelViewController.collapseWithDuration(duration);
-    }
-
     /**
      * Updates the light reveal effect to reflect the reason we're waking or sleeping (for example,
      * from the power button).
@@ -3481,15 +3440,6 @@
         mNavigationBarController.showPinningEscapeToast(mDisplayId);
     }
 
-    /**
-     * TODO: Remove this method. Views should not be passed forward. Will cause theme issues.
-     * @return bottom area view
-     */
-    @Override
-    public KeyguardBottomAreaView getKeyguardBottomAreaView() {
-        return mNotificationPanelViewController.getKeyguardBottomAreaView();
-    }
-
     protected ViewRootImpl getViewRootImpl()  {
         NotificationShadeWindowView nswv = getNotificationShadeWindowView();
         if (nswv != null) return nswv.getViewRootImpl();
@@ -4192,23 +4142,11 @@
         return mBouncerShowingOverDream;
     }
 
-    /**
-     * When {@link KeyguardBouncer} starts to be dismissed, playing its animation.
-     */
-    @Override
-    public void onBouncerPreHideAnimation() {
-        mNotificationPanelViewController.onBouncerPreHideAnimation();
-
-    }
-
     @Override
     public boolean isKeyguardSecure() {
         return mStatusBarKeyguardViewManager.isSecure();
     }
-    @Override
-    public NotificationPanelViewController getPanelController() {
-        return mNotificationPanelViewController;
-    }
+
     // End Extra BaseStatusBarMethods.
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
index 37f04bb..a28fca1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardBouncer.java
@@ -64,6 +64,11 @@
     private static final String TAG = "KeyguardBouncer";
     static final long BOUNCER_FACE_DELAY = 1200;
     public static final float ALPHA_EXPANSION_THRESHOLD = 0.95f;
+    /**
+     * Values for the bouncer expansion represented as the panel expansion.
+     * Panel expansion 1f = panel fully showing = bouncer fully hidden
+     * Panel expansion 0f = panel fully hiding = bouncer fully showing
+     */
     public static final float EXPANSION_HIDDEN = 1f;
     public static final float EXPANSION_VISIBLE = 0f;
 
@@ -143,6 +148,14 @@
     }
 
     /**
+     * Get the KeyguardBouncer expansion
+     * @return 1=HIDDEN, 0=SHOWING, in between 0 and 1 means the bouncer is in transition.
+     */
+    public float getExpansion() {
+        return mExpansion;
+    }
+
+    /**
      * Enable/disable only the back button
      */
     public void setBackButtonEnabled(boolean enabled) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
index 5e26cf0..4550cb2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/KeyguardLiftController.kt
@@ -73,7 +73,6 @@
             isListening = false
             updateListeningState()
             keyguardUpdateMonitor.requestFaceAuth(
-                true,
                 FaceAuthApiRequestReason.PICK_UP_GESTURE_TRIGGERED
             )
             keyguardUpdateMonitor.requestActiveUnlock(
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java
index 6e98c49..eba7fe0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LightsOutNotifController.java
@@ -22,8 +22,8 @@
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
-import android.view.InsetsVisibilities;
 import android.view.View;
+import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowInsetsController.Behavior;
 import android.view.WindowManager;
@@ -144,7 +144,7 @@
         @Override
         public void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
                 AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
-                @Behavior int behavior, InsetsVisibilities requestedVisibilities,
+                @Behavior int behavior, @InsetsType int requestedVisibleTypes,
                 String packageName, LetterboxDetails[] letterboxDetails) {
             if (displayId != mDisplayId) {
                 return;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
index 86f6ff8..0a0ded2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconController.java
@@ -18,6 +18,7 @@
 import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE;
 import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_MOBILE_NEW;
 import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_WIFI;
+import static com.android.systemui.statusbar.phone.StatusBarIconHolder.TYPE_WIFI_NEW;
 
 import android.annotation.Nullable;
 import android.content.Context;
@@ -53,8 +54,9 @@
 import com.android.systemui.statusbar.pipeline.mobile.ui.binder.MobileIconsBinder;
 import com.android.systemui.statusbar.pipeline.mobile.ui.view.ModernStatusBarMobileView;
 import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel;
+import com.android.systemui.statusbar.pipeline.wifi.ui.WifiUiAdapter;
 import com.android.systemui.statusbar.pipeline.wifi.ui.view.ModernStatusBarWifiView;
-import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel;
+import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel;
 import com.android.systemui.util.Assert;
 
 import java.util.ArrayList;
@@ -84,7 +86,18 @@
     /** */
     void setIcon(String slot, StatusBarIcon icon);
     /** */
-    void setSignalIcon(String slot, WifiIconState state);
+    void setWifiIcon(String slot, WifiIconState state);
+
+    /**
+     * Sets up a wifi icon using the new data pipeline. No effect if the wifi icon has already been
+     * set up (inflated and added to the view hierarchy).
+     *
+     * This method completely replaces {@link #setWifiIcon} with the information from the new wifi
+     * data pipeline. Icons will automatically keep their state up to date, so we don't have to
+     * worry about funneling state objects through anymore.
+     */
+    void setNewWifiIcon();
+
     /** */
     void setMobileIcons(String slot, List<MobileIconState> states);
 
@@ -151,14 +164,14 @@
                 LinearLayout linearLayout,
                 StatusBarLocation location,
                 StatusBarPipelineFlags statusBarPipelineFlags,
-                WifiViewModel wifiViewModel,
+                WifiUiAdapter wifiUiAdapter,
                 MobileUiAdapter mobileUiAdapter,
                 MobileContextProvider mobileContextProvider,
                 DarkIconDispatcher darkIconDispatcher) {
             super(linearLayout,
                     location,
                     statusBarPipelineFlags,
-                    wifiViewModel,
+                    wifiUiAdapter,
                     mobileUiAdapter,
                     mobileContextProvider);
             mIconHPadding = mContext.getResources().getDimensionPixelSize(
@@ -218,7 +231,7 @@
         @SysUISingleton
         public static class Factory {
             private final StatusBarPipelineFlags mStatusBarPipelineFlags;
-            private final WifiViewModel mWifiViewModel;
+            private final WifiUiAdapter mWifiUiAdapter;
             private final MobileContextProvider mMobileContextProvider;
             private final MobileUiAdapter mMobileUiAdapter;
             private final DarkIconDispatcher mDarkIconDispatcher;
@@ -226,12 +239,12 @@
             @Inject
             public Factory(
                     StatusBarPipelineFlags statusBarPipelineFlags,
-                    WifiViewModel wifiViewModel,
+                    WifiUiAdapter wifiUiAdapter,
                     MobileContextProvider mobileContextProvider,
                     MobileUiAdapter mobileUiAdapter,
                     DarkIconDispatcher darkIconDispatcher) {
                 mStatusBarPipelineFlags = statusBarPipelineFlags;
-                mWifiViewModel = wifiViewModel;
+                mWifiUiAdapter = wifiUiAdapter;
                 mMobileContextProvider = mobileContextProvider;
                 mMobileUiAdapter = mobileUiAdapter;
                 mDarkIconDispatcher = darkIconDispatcher;
@@ -242,7 +255,7 @@
                         group,
                         location,
                         mStatusBarPipelineFlags,
-                        mWifiViewModel,
+                        mWifiUiAdapter,
                         mMobileUiAdapter,
                         mMobileContextProvider,
                         mDarkIconDispatcher);
@@ -260,14 +273,14 @@
                 ViewGroup group,
                 StatusBarLocation location,
                 StatusBarPipelineFlags statusBarPipelineFlags,
-                WifiViewModel wifiViewModel,
+                WifiUiAdapter wifiUiAdapter,
                 MobileUiAdapter mobileUiAdapter,
                 MobileContextProvider mobileContextProvider
         ) {
             super(group,
                     location,
                     statusBarPipelineFlags,
-                    wifiViewModel,
+                    wifiUiAdapter,
                     mobileUiAdapter,
                     mobileContextProvider);
         }
@@ -302,19 +315,19 @@
         @SysUISingleton
         public static class Factory {
             private final StatusBarPipelineFlags mStatusBarPipelineFlags;
-            private final WifiViewModel mWifiViewModel;
+            private final WifiUiAdapter mWifiUiAdapter;
             private final MobileContextProvider mMobileContextProvider;
             private final MobileUiAdapter mMobileUiAdapter;
 
             @Inject
             public Factory(
                     StatusBarPipelineFlags statusBarPipelineFlags,
-                    WifiViewModel wifiViewModel,
+                    WifiUiAdapter wifiUiAdapter,
                     MobileUiAdapter mobileUiAdapter,
                     MobileContextProvider mobileContextProvider
             ) {
                 mStatusBarPipelineFlags = statusBarPipelineFlags;
-                mWifiViewModel = wifiViewModel;
+                mWifiUiAdapter = wifiUiAdapter;
                 mMobileUiAdapter = mobileUiAdapter;
                 mMobileContextProvider = mobileContextProvider;
             }
@@ -324,7 +337,7 @@
                         group,
                         location,
                         mStatusBarPipelineFlags,
-                        mWifiViewModel,
+                        mWifiUiAdapter,
                         mMobileUiAdapter,
                         mMobileContextProvider);
             }
@@ -336,10 +349,9 @@
      */
     class IconManager implements DemoModeCommandReceiver {
         protected final ViewGroup mGroup;
-        private final StatusBarLocation mLocation;
         private final StatusBarPipelineFlags mStatusBarPipelineFlags;
-        private final WifiViewModel mWifiViewModel;
         private final MobileContextProvider mMobileContextProvider;
+        private final LocationBasedWifiViewModel mWifiViewModel;
         private final MobileIconsViewModel mMobileIconsViewModel;
 
         protected final Context mContext;
@@ -359,26 +371,33 @@
                 ViewGroup group,
                 StatusBarLocation location,
                 StatusBarPipelineFlags statusBarPipelineFlags,
-                WifiViewModel wifiViewModel,
+                WifiUiAdapter wifiUiAdapter,
                 MobileUiAdapter mobileUiAdapter,
                 MobileContextProvider mobileContextProvider
         ) {
             mGroup = group;
-            mLocation = location;
             mStatusBarPipelineFlags = statusBarPipelineFlags;
-            mWifiViewModel = wifiViewModel;
             mMobileContextProvider = mobileContextProvider;
             mContext = group.getContext();
             mIconSize = mContext.getResources().getDimensionPixelSize(
                     com.android.internal.R.dimen.status_bar_icon_size);
 
-            if (statusBarPipelineFlags.useNewMobileIcons()) {
-                // This starts the flow for the new pipeline, and will notify us of changes
+            if (statusBarPipelineFlags.runNewMobileIconsBackend()) {
+                // This starts the flow for the new pipeline, and will notify us of changes if
+                // {@link StatusBarPipelineFlags#useNewMobileIcons} is also true.
                 mMobileIconsViewModel = mobileUiAdapter.createMobileIconsViewModel();
                 MobileIconsBinder.bind(mGroup, mMobileIconsViewModel);
             } else {
                 mMobileIconsViewModel = null;
             }
+
+            if (statusBarPipelineFlags.runNewWifiIconBackend()) {
+                // This starts the flow for the new pipeline, and will notify us of changes if
+                // {@link StatusBarPipelineFlags#useNewWifiIcon} is also true.
+                mWifiViewModel = wifiUiAdapter.bindGroup(mGroup, location);
+            } else {
+                mWifiViewModel = null;
+            }
         }
 
         public boolean isDemoable() {
@@ -429,6 +448,9 @@
                 case TYPE_WIFI:
                     return addWifiIcon(index, slot, holder.getWifiState());
 
+                case TYPE_WIFI_NEW:
+                    return addNewWifiIcon(index, slot);
+
                 case TYPE_MOBILE:
                     return addMobileIcon(index, slot, holder.getMobileState());
 
@@ -450,16 +472,13 @@
 
         @VisibleForTesting
         protected StatusIconDisplayable addWifiIcon(int index, String slot, WifiIconState state) {
-            final BaseStatusBarFrameLayout view;
             if (mStatusBarPipelineFlags.useNewWifiIcon()) {
-                view = onCreateModernStatusBarWifiView(slot);
-                // When [ModernStatusBarWifiView] is created, it will automatically apply the
-                // correct view state so we don't need to call applyWifiState.
-            } else {
-                StatusBarWifiView wifiView = onCreateStatusBarWifiView(slot);
-                wifiView.applyWifiState(state);
-                view = wifiView;
+                throw new IllegalStateException("Attempting to add a mobile icon while the new "
+                        + "icons are enabled is not supported");
             }
+
+            final StatusBarWifiView view = onCreateStatusBarWifiView(slot);
+            view.applyWifiState(state);
             mGroup.addView(view, index, onCreateLayoutParams());
 
             if (mIsInDemoMode) {
@@ -468,6 +487,17 @@
             return view;
         }
 
+        protected StatusIconDisplayable addNewWifiIcon(int index, String slot) {
+            if (!mStatusBarPipelineFlags.useNewWifiIcon()) {
+                throw new IllegalStateException("Attempting to add a wifi icon using the new"
+                        + "pipeline, but the enabled flag is false.");
+            }
+
+            ModernStatusBarWifiView view = onCreateModernStatusBarWifiView(slot);
+            mGroup.addView(view, index, onCreateLayoutParams());
+            return view;
+        }
+
         @VisibleForTesting
         protected StatusIconDisplayable addMobileIcon(
                 int index,
@@ -523,8 +553,7 @@
         }
 
         private ModernStatusBarWifiView onCreateModernStatusBarWifiView(String slot) {
-            return ModernStatusBarWifiView.constructAndBind(
-                    mContext, slot, mWifiViewModel, mLocation);
+            return ModernStatusBarWifiView.constructAndBind(mContext, slot, mWifiViewModel);
         }
 
         private StatusBarMobileView onCreateStatusBarMobileView(int subId, String slot) {
@@ -600,7 +629,8 @@
                     onSetMobileIcon(viewIndex, holder.getMobileState());
                     return;
                 case TYPE_MOBILE_NEW:
-                    // Nothing, the icon updates itself now
+                case TYPE_WIFI_NEW:
+                    // Nothing, the new icons update themselves
                     return;
                 default:
                     break;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
index 31e960a..674e574 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconControllerImpl.java
@@ -195,12 +195,13 @@
         }
     }
 
-    /**
-     * Signal icons need to be handled differently, because they can be
-     * composite views
-     */
     @Override
-    public void setSignalIcon(String slot, WifiIconState state) {
+    public void setWifiIcon(String slot, WifiIconState state) {
+        if (mStatusBarPipelineFlags.useNewWifiIcon()) {
+            Log.d(TAG, "ignoring old pipeline callback because the new wifi icon is enabled");
+            return;
+        }
+
         if (state == null) {
             removeIcon(slot, 0);
             return;
@@ -216,6 +217,24 @@
         }
     }
 
+
+    @Override
+    public void setNewWifiIcon() {
+        if (!mStatusBarPipelineFlags.useNewWifiIcon()) {
+            Log.d(TAG, "ignoring new pipeline callback because the new wifi icon is disabled");
+            return;
+        }
+
+        String slot = mContext.getString(com.android.internal.R.string.status_bar_wifi);
+        StatusBarIconHolder holder = mStatusBarIconList.getIconHolder(slot, /* tag= */ 0);
+        if (holder == null) {
+            holder = StatusBarIconHolder.forNewWifiIcon();
+            setIcon(slot, holder);
+        } else {
+            // Don't have to do anything in the new world
+        }
+    }
+
     /**
      * Accept a list of MobileIconStates, which all live in the same slot(?!), and then are sorted
      * by subId. Don't worry this definitely makes sense and works.
@@ -225,7 +244,7 @@
     @Override
     public void setMobileIcons(String slot, List<MobileIconState> iconStates) {
         if (mStatusBarPipelineFlags.useNewMobileIcons()) {
-            Log.d(TAG, "ignoring old pipeline callbacks, because the new "
+            Log.d(TAG, "ignoring old pipeline callbacks, because the new mobile "
                     + "icons are enabled");
             return;
         }
@@ -251,10 +270,11 @@
     public void setNewMobileIconSubIds(List<Integer> subIds) {
         if (!mStatusBarPipelineFlags.useNewMobileIcons()) {
             Log.d(TAG, "ignoring new pipeline callback, "
-                    + "since the new icons are disabled");
+                    + "since the new mobile icons are disabled");
             return;
         }
-        Slot mobileSlot = mStatusBarIconList.getSlot("mobile");
+        String slotName = mContext.getString(com.android.internal.R.string.status_bar_mobile);
+        Slot mobileSlot = mStatusBarIconList.getSlot(slotName);
 
         Collections.reverse(subIds);
 
@@ -262,7 +282,7 @@
             StatusBarIconHolder holder = mobileSlot.getHolderForTag(subId);
             if (holder == null) {
                 holder = StatusBarIconHolder.fromSubIdForModernMobileIcon(subId);
-                setIcon("mobile", holder);
+                setIcon(slotName, holder);
             } else {
                 // Don't have to do anything in the new world
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
index 68a203e..f6c0da8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarIconHolder.java
@@ -51,11 +51,24 @@
     @Deprecated
     public static final int TYPE_MOBILE_NEW = 3;
 
+    /**
+     * TODO (b/238425913): address this once the new pipeline is in place
+     * This type exists so that the new wifi pipeline can be used to inform the old view system
+     * about the existence of the wifi icon. The design of the new pipeline should allow for removal
+     * of this icon holder type, and obsolete the need for this entire class.
+     *
+     * @deprecated This field only exists so the new status bar pipeline can interface with the
+     * view holder system.
+     */
+    @Deprecated
+    public static final int TYPE_WIFI_NEW = 4;
+
     @IntDef({
             TYPE_ICON,
             TYPE_WIFI,
             TYPE_MOBILE,
-            TYPE_MOBILE_NEW
+            TYPE_MOBILE_NEW,
+            TYPE_WIFI_NEW
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface IconType {}
@@ -95,6 +108,13 @@
         return holder;
     }
 
+    /** Creates a new holder with for the new wifi icon. */
+    public static StatusBarIconHolder forNewWifiIcon() {
+        StatusBarIconHolder holder = new StatusBarIconHolder();
+        holder.mType = TYPE_WIFI_NEW;
+        return holder;
+    }
+
     /** */
     public static StatusBarIconHolder fromMobileIconState(MobileIconState state) {
         StatusBarIconHolder holder = new StatusBarIconHolder();
@@ -172,9 +192,10 @@
             case TYPE_MOBILE:
                 return mMobileState.visible;
             case TYPE_MOBILE_NEW:
-                //TODO (b/249790733), the new pipeline can control visibility via the ViewModel
+            case TYPE_WIFI_NEW:
+                // The new pipeline controls visibilities via the view model and view binder, so
+                // this is effectively an unused return value.
                 return true;
-
             default:
                 return true;
         }
@@ -199,7 +220,9 @@
                 break;
 
             case TYPE_MOBILE_NEW:
-                //TODO (b/249790733), the new pipeline can control visibility via the ViewModel
+            case TYPE_WIFI_NEW:
+                // The new pipeline controls visibilities via the view model and view binder, so
+                // ignore setVisible.
                 break;
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 5480f5d..93b6437 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -86,8 +86,10 @@
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.HashSet;
 import java.util.Objects;
 import java.util.Optional;
+import java.util.Set;
 
 import javax.inject.Inject;
 
@@ -166,13 +168,9 @@
 
         @Override
         public void onExpansionChanged(float expansion) {
-            if (mAlternateAuthInterceptor != null) {
-                mAlternateAuthInterceptor.setBouncerExpansionChanged(expansion);
-            }
             if (mBouncerAnimating) {
                 mCentralSurfaces.setBouncerHiddenFraction(expansion);
             }
-            updateStates();
         }
 
         @Override
@@ -184,9 +182,6 @@
             if (!isVisible) {
                 mCentralSurfaces.setBouncerHiddenFraction(KeyguardBouncer.EXPANSION_HIDDEN);
             }
-            if (mAlternateAuthInterceptor != null) {
-                mAlternateAuthInterceptor.onBouncerVisibilityChanged();
-            }
 
             /* Register predictive back callback when keyguard becomes visible, and unregister
             when it's hidden. */
@@ -252,6 +247,7 @@
     private int mLastBiometricMode;
     private boolean mLastScreenOffAnimationPlaying;
     private float mQsExpansion;
+    final Set<KeyguardViewManagerCallback> mCallbacks = new HashSet<>();
     private boolean mIsModernBouncerEnabled;
 
     private OnDismissAction mAfterKeyguardGoneAction;
@@ -465,7 +461,7 @@
             if (mBouncer != null) {
                 mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
             } else {
-                mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
+                mBouncerInteractor.setPanelExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
             }
         } else if (mStatusBarStateController.getState() == StatusBarState.SHADE_LOCKED) {
             // Don't expand to the bouncer. Instead transition back to the lock screen (see
@@ -475,17 +471,17 @@
             if (mBouncer != null) {
                 mBouncer.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
             } else {
-                mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
+                mBouncerInteractor.setPanelExpansion(KeyguardBouncer.EXPANSION_VISIBLE);
             }
         } else if (mKeyguardStateController.isShowing()  && !hideBouncerOverDream) {
             if (!isWakeAndUnlocking()
                     && !(mBiometricUnlockController.getMode() == MODE_DISMISS_BOUNCER)
-                    && !mCentralSurfaces.isInLaunchTransition()
+                    && !mNotificationPanelViewController.isLaunchTransitionFinished()
                     && !isUnlockCollapsing()) {
                 if (mBouncer != null) {
                     mBouncer.setExpansion(fraction);
                 } else {
-                    mBouncerInteractor.setExpansion(fraction);
+                    mBouncerInteractor.setPanelExpansion(fraction);
                 }
             }
             if (fraction != KeyguardBouncer.EXPANSION_HIDDEN && tracking
@@ -504,7 +500,7 @@
             if (mBouncer != null) {
                 mBouncer.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
             } else {
-                mBouncerInteractor.setExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
+                mBouncerInteractor.setPanelExpansion(KeyguardBouncer.EXPANSION_HIDDEN);
             }
         } else if (mPulsing && fraction == KeyguardBouncer.EXPANSION_VISIBLE) {
             // Panel expanded while pulsing but didn't translate the bouncer (because we are
@@ -849,7 +845,7 @@
         if (isShowing && isOccluding) {
             SysUiStatsLog.write(SysUiStatsLog.KEYGUARD_STATE_CHANGED,
                     SysUiStatsLog.KEYGUARD_STATE_CHANGED__STATE__OCCLUDED);
-            if (mCentralSurfaces.isInLaunchTransition()) {
+            if (mNotificationPanelViewController.isLaunchTransitionFinished()) {
                 final Runnable endRunnable = new Runnable() {
                     @Override
                     public void run() {
@@ -903,7 +899,7 @@
             } else {
                 mBouncerInteractor.startDisappearAnimation(finishRunnable);
             }
-            mCentralSurfaces.onBouncerPreHideAnimation();
+            mNotificationPanelViewController.startBouncerPreHideAnimation();
 
             // We update the state (which will show the keyguard) only if an animation will run on
             // the keyguard. If there is no animation, we wait before updating the state so that we
@@ -935,7 +931,7 @@
         long uptimeMillis = SystemClock.uptimeMillis();
         long delay = Math.max(0, startTime + HIDE_TIMING_CORRECTION_MS - uptimeMillis);
 
-        if (mCentralSurfaces.isInLaunchTransition()
+        if (mNotificationPanelViewController.isLaunchTransitionFinished()
                 || mKeyguardStateController.isFlingingToDismissKeyguard()) {
             final boolean wasFlingingToDismissKeyguard =
                     mKeyguardStateController.isFlingingToDismissKeyguard();
@@ -1312,7 +1308,7 @@
 
     @Override
     public boolean shouldDisableWindowAnimationsForUnlock() {
-        return mCentralSurfaces.isInLaunchTransition();
+        return mNotificationPanelViewController.isLaunchTransitionFinished();
     }
 
     @Override
@@ -1355,7 +1351,7 @@
             mBouncerInteractor.notifyKeyguardAuthenticated(strongAuth);
         }
 
-        if (mAlternateAuthInterceptor != null && isShowingAlternateAuthOrAnimating()) {
+        if (mAlternateAuthInterceptor != null && isShowingAlternateAuth()) {
             resetAlternateAuth(false);
             executeAfterKeyguardGoneAction();
         }
@@ -1441,6 +1437,10 @@
         pw.println("  mPendingWakeupAction: " + mPendingWakeupAction);
         pw.println("  isBouncerShowing(): " + isBouncerShowing());
         pw.println("  bouncerIsOrWillBeShowing(): " + bouncerIsOrWillBeShowing());
+        pw.println("  Registered KeyguardViewManagerCallbacks:");
+        for (KeyguardViewManagerCallback callback : mCallbacks) {
+            pw.println("      " + callback);
+        }
 
         if (mBouncer != null) {
             mBouncer.dump(pw);
@@ -1465,6 +1465,20 @@
     }
 
     /**
+     * Add a callback to listen for changes
+     */
+    public void addCallback(KeyguardViewManagerCallback callback) {
+        mCallbacks.add(callback);
+    }
+
+    /**
+     * Removes callback to stop receiving updates
+     */
+    public void removeCallback(KeyguardViewManagerCallback callback) {
+        mCallbacks.remove(callback);
+    }
+
+    /**
      * Whether qs is currently expanded.
      */
     public float getQsExpansion() {
@@ -1476,8 +1490,8 @@
      */
     public void setQsExpansion(float qsExpansion) {
         mQsExpansion = qsExpansion;
-        if (mAlternateAuthInterceptor != null) {
-            mAlternateAuthInterceptor.setQsExpansion(qsExpansion);
+        for (KeyguardViewManagerCallback callback : mCallbacks) {
+            callback.onQSExpansionChanged(mQsExpansion);
         }
     }
 
@@ -1491,21 +1505,13 @@
                 && mAlternateAuthInterceptor.isShowingAlternateAuthBouncer();
     }
 
-    public boolean isShowingAlternateAuthOrAnimating() {
-        return mAlternateAuthInterceptor != null
-                && (mAlternateAuthInterceptor.isShowingAlternateAuthBouncer()
-                || mAlternateAuthInterceptor.isAnimating());
-    }
-
     /**
-     * Forward touches to any alternate authentication affordances.
+     * Forward touches to callbacks.
      */
-    public boolean onTouch(MotionEvent event) {
-        if (mAlternateAuthInterceptor == null) {
-            return false;
+    public void onTouch(MotionEvent event) {
+        for (KeyguardViewManagerCallback callback: mCallbacks) {
+            callback.onTouch(event);
         }
-
-        return mAlternateAuthInterceptor.onTouch(event);
     }
 
     /** Update keyguard position based on a tapped X coordinate. */
@@ -1639,38 +1645,6 @@
         boolean isShowingAlternateAuthBouncer();
 
         /**
-         * print information for the alternate auth interceptor registered
-         */
-        void dump(PrintWriter pw);
-
-        /**
-         * @return true if the new auth method bouncer is currently animating in or out.
-         */
-        boolean isAnimating();
-
-        /**
-         * How much QS is fully expanded where 0f is not showing and 1f is fully expanded.
-         */
-        void setQsExpansion(float qsExpansion);
-
-        /**
-         * Forward potential touches to authentication interceptor
-         * @return true if event was handled
-         */
-        boolean onTouch(MotionEvent event);
-
-        /**
-         * Update pin/pattern/password bouncer expansion amount where 0 is visible and 1 is fully
-         * hidden
-         */
-        void setBouncerExpansionChanged(float expansion);
-
-        /**
-         *  called when the bouncer view visibility has changed.
-         */
-        void onBouncerVisibilityChanged();
-
-        /**
          * Use when an app occluding the keyguard would like to give the user ability to
          * unlock the device using udfps.
          *
@@ -1679,5 +1653,25 @@
          */
         void requestUdfps(boolean requestUdfps, int color);
 
+        /**
+         * print information for the alternate auth interceptor registered
+         */
+        void dump(PrintWriter pw);
+    }
+
+    /**
+     * Callback for KeyguardViewManager state changes.
+     */
+    public interface KeyguardViewManagerCallback {
+        /**
+         * Set the amount qs is expanded. For example, swipe down from the top of the
+         * lock screen to start the full QS expansion.
+         */
+        default void onQSExpansionChanged(float qsExpansion) { }
+
+        /**
+         * Forward touch events to callbacks
+         */
+        default void onTouch(MotionEvent event) { }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
index ee948c0..b1642d6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarLaunchAnimatorController.kt
@@ -31,7 +31,7 @@
         delegate.onLaunchAnimationStart(isExpandingFullyAbove)
         centralSurfaces.notificationPanelViewController.setIsLaunchAnimationRunning(true)
         if (!isExpandingFullyAbove) {
-            centralSurfaces.collapsePanelWithDuration(
+            centralSurfaces.notificationPanelViewController.collapseWithDuration(
                 ActivityLaunchAnimator.TIMINGS.totalDuration.toInt())
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
index 5cd2ba1..b6ae4a0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -23,7 +23,6 @@
 import android.app.ActivityManager;
 import android.app.KeyguardManager;
 import android.app.Notification;
-import android.app.NotificationManager;
 import android.app.PendingIntent;
 import android.app.TaskStackBuilder;
 import android.content.Context;
@@ -60,9 +59,8 @@
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.notification.NotificationActivityStarter;
 import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
-import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
-import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
+import com.android.systemui.statusbar.notification.collection.provider.LaunchFullScreenIntentProvider;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -126,7 +124,6 @@
             Context context,
             Handler mainThreadHandler,
             Executor uiBgExecutor,
-            NotifPipeline notifPipeline,
             NotificationVisibilityProvider visibilityProvider,
             HeadsUpManagerPhone headsUpManager,
             ActivityStarter activityStarter,
@@ -151,7 +148,8 @@
             NotificationPresenter presenter,
             NotificationPanelViewController panel,
             ActivityLaunchAnimator activityLaunchAnimator,
-            NotificationLaunchAnimatorControllerProvider notificationAnimationProvider) {
+            NotificationLaunchAnimatorControllerProvider notificationAnimationProvider,
+            LaunchFullScreenIntentProvider launchFullScreenIntentProvider) {
         mContext = context;
         mMainThreadHandler = mainThreadHandler;
         mUiBgExecutor = uiBgExecutor;
@@ -182,12 +180,7 @@
         mActivityLaunchAnimator = activityLaunchAnimator;
         mNotificationAnimationProvider = notificationAnimationProvider;
 
-        notifPipeline.addCollectionListener(new NotifCollectionListener() {
-            @Override
-            public void onEntryAdded(NotificationEntry entry) {
-                handleFullScreenIntent(entry);
-            }
-        });
+        launchFullScreenIntentProvider.registerListener(entry -> launchFullScreenIntent(entry));
     }
 
     /**
@@ -549,38 +542,36 @@
     }
 
     @VisibleForTesting
-    void handleFullScreenIntent(NotificationEntry entry) {
-        if (mNotificationInterruptStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry)) {
-            if (shouldSuppressFullScreenIntent(entry)) {
-                mLogger.logFullScreenIntentSuppressedByDnD(entry);
-            } else if (entry.getImportance() < NotificationManager.IMPORTANCE_HIGH) {
-                mLogger.logFullScreenIntentNotImportantEnough(entry);
-            } else {
-                // Stop screensaver if the notification has a fullscreen intent.
-                // (like an incoming phone call)
-                mUiBgExecutor.execute(() -> {
-                    try {
-                        mDreamManager.awaken();
-                    } catch (RemoteException e) {
-                        e.printStackTrace();
-                    }
-                });
+    void launchFullScreenIntent(NotificationEntry entry) {
+        // Skip if device is in VR mode.
+        if (mPresenter.isDeviceInVrMode()) {
+            mLogger.logFullScreenIntentSuppressedByVR(entry);
+            return;
+        }
 
-                // not immersive & a fullscreen alert should be shown
-                final PendingIntent fullscreenIntent =
-                        entry.getSbn().getNotification().fullScreenIntent;
-                mLogger.logSendingFullScreenIntent(entry, fullscreenIntent);
-                try {
-                    EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION,
-                            entry.getKey());
-                    mCentralSurfaces.wakeUpForFullScreenIntent();
-                    fullscreenIntent.send();
-                    entry.notifyFullScreenIntentLaunched();
-                    mMetricsLogger.count("note_fullscreen", 1);
-                } catch (PendingIntent.CanceledException e) {
-                    // ignore
-                }
+        // Stop screensaver if the notification has a fullscreen intent.
+        // (like an incoming phone call)
+        mUiBgExecutor.execute(() -> {
+            try {
+                mDreamManager.awaken();
+            } catch (RemoteException e) {
+                e.printStackTrace();
             }
+        });
+
+        // not immersive & a fullscreen alert should be shown
+        final PendingIntent fullscreenIntent =
+                entry.getSbn().getNotification().fullScreenIntent;
+        mLogger.logSendingFullScreenIntent(entry, fullscreenIntent);
+        try {
+            EventLog.writeEvent(EventLogTags.SYSUI_FULLSCREEN_NOTIFICATION,
+                    entry.getKey());
+            mCentralSurfaces.wakeUpForFullScreenIntent();
+            fullscreenIntent.send();
+            entry.notifyFullScreenIntentLaunched();
+            mMetricsLogger.count("note_fullscreen", 1);
+        } catch (PendingIntent.CanceledException e) {
+            // ignore
         }
     }
 
@@ -607,12 +598,4 @@
             mMainThreadHandler.post(mShadeController::collapsePanel);
         }
     }
-
-    private boolean shouldSuppressFullScreenIntent(NotificationEntry entry) {
-        if (mPresenter.isDeviceInVrMode()) {
-            return true;
-        }
-
-        return entry.shouldSuppressFullScreenIntent();
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt
index 81edff4..1f0b96a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterLogger.kt
@@ -96,19 +96,11 @@
         })
     }
 
-    fun logFullScreenIntentSuppressedByDnD(entry: NotificationEntry) {
+    fun logFullScreenIntentSuppressedByVR(entry: NotificationEntry) {
         buffer.log(TAG, DEBUG, {
             str1 = entry.logKey
         }, {
-            "No Fullscreen intent: suppressed by DND: $str1"
-        })
-    }
-
-    fun logFullScreenIntentNotImportantEnough(entry: NotificationEntry) {
-        buffer.log(TAG, DEBUG, {
-            str1 = entry.logKey
-        }, {
-            "No Fullscreen intent: not important enough: $str1"
+            "No Fullscreen intent: suppressed by VR mode: $str1"
         })
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
index 492734e..de7bf3c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarSignalPolicy.java
@@ -212,7 +212,7 @@
     private void updateWifiIconWithState(WifiIconState state) {
         if (DEBUG) Log.d(TAG, "WifiIconState: " + state == null ? "" : state.toString());
         if (state.visible && state.resId > 0) {
-            mIconController.setSignalIcon(mSlotWifi, state);
+            mIconController.setWifiIcon(mSlotWifi, state);
             mIconController.setIconVisibility(mSlotWifi, true);
         } else {
             mIconController.setIconVisibility(mSlotWifi, false);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt
index 6cd8c78..08599c2 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemBarAttributesListener.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.statusbar.phone
 
-import android.view.InsetsVisibilities
+import android.view.WindowInsets.Type.InsetsType
 import android.view.WindowInsetsController.Appearance
 import android.view.WindowInsetsController.Behavior
 import com.android.internal.statusbar.LetterboxDetails
@@ -66,21 +66,21 @@
                 params.appearanceRegionsArray,
                 params.navbarColorManagedByIme,
                 params.behavior,
-                params.requestedVisibilities,
+                params.requestedVisibleTypes,
                 params.packageName,
                 params.letterboxesArray)
         }
     }
 
     fun onSystemBarAttributesChanged(
-        displayId: Int,
-        @Appearance originalAppearance: Int,
-        originalAppearanceRegions: Array<AppearanceRegion>,
-        navbarColorManagedByIme: Boolean,
-        @Behavior behavior: Int,
-        requestedVisibilities: InsetsVisibilities,
-        packageName: String,
-        letterboxDetails: Array<LetterboxDetails>
+            displayId: Int,
+            @Appearance originalAppearance: Int,
+            originalAppearanceRegions: Array<AppearanceRegion>,
+            navbarColorManagedByIme: Boolean,
+            @Behavior behavior: Int,
+            @InsetsType requestedVisibleTypes: Int,
+            packageName: String,
+            letterboxDetails: Array<LetterboxDetails>
     ) {
         lastSystemBarAttributesParams =
             SystemBarAttributesParams(
@@ -89,7 +89,7 @@
                 originalAppearanceRegions.toList(),
                 navbarColorManagedByIme,
                 behavior,
-                requestedVisibilities,
+                requestedVisibleTypes,
                 packageName,
                 letterboxDetails.toList())
 
@@ -104,7 +104,7 @@
 
         centralSurfaces.updateBubblesVisibility()
         statusBarStateController.setSystemBarAttributes(
-            appearance, behavior, requestedVisibilities, packageName)
+            appearance, behavior, requestedVisibleTypes, packageName)
     }
 
     private fun modifyAppearanceIfNeeded(
@@ -137,14 +137,14 @@
  * [SystemBarAttributesListener.onSystemBarAttributesChanged].
  */
 private data class SystemBarAttributesParams(
-    val displayId: Int,
-    @Appearance val appearance: Int,
-    val appearanceRegions: List<AppearanceRegion>,
-    val navbarColorManagedByIme: Boolean,
-    @Behavior val behavior: Int,
-    val requestedVisibilities: InsetsVisibilities,
-    val packageName: String,
-    val letterboxes: List<LetterboxDetails>,
+        val displayId: Int,
+        @Appearance val appearance: Int,
+        val appearanceRegions: List<AppearanceRegion>,
+        val navbarColorManagedByIme: Boolean,
+        @Behavior val behavior: Int,
+        @InsetsType val requestedVisibleTypes: Int,
+        val packageName: String,
+        val letterboxes: List<LetterboxDetails>,
 ) {
     val letterboxesArray = letterboxes.toTypedArray()
     val appearanceRegionsArray = appearanceRegions.toTypedArray()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
index 06cd12d..946d7e4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/StatusBarPipelineFlags.kt
@@ -27,11 +27,26 @@
     /** True if we should display the mobile icons using the new status bar data pipeline. */
     fun useNewMobileIcons(): Boolean = featureFlags.isEnabled(Flags.NEW_STATUS_BAR_MOBILE_ICONS)
 
+    /**
+     * True if we should run the new mobile icons backend to get the logging.
+     *
+     * Does *not* affect whether we render the mobile icons using the new backend data. See
+     * [useNewMobileIcons] for that.
+     */
+    fun runNewMobileIconsBackend(): Boolean =
+        featureFlags.isEnabled(Flags.NEW_STATUS_BAR_MOBILE_ICONS_BACKEND) || useNewMobileIcons()
+
     /** True if we should display the wifi icon using the new status bar data pipeline. */
     fun useNewWifiIcon(): Boolean = featureFlags.isEnabled(Flags.NEW_STATUS_BAR_WIFI_ICON)
 
-    // TODO(b/238425913): Add flags to only run the mobile backend or wifi backend so we get the
-    //   logging without getting the UI effects.
+    /**
+     * True if we should run the new wifi icon backend to get the logging.
+     *
+     * Does *not* affect whether we render the wifi icon using the new backend data. See
+     * [useNewWifiIcon] for that.
+     */
+    fun runNewWifiIconBackend(): Boolean =
+        featureFlags.isEnabled(Flags.NEW_STATUS_BAR_WIFI_ICON_BACKEND) || useNewWifiIcon()
 
     /**
      * Returns true if we should apply some coloring to the wifi icon that was rendered with the new
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/DataConnectionState.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/DataConnectionState.kt
new file mode 100644
index 0000000..da87f73
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/DataConnectionState.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.data.model
+
+import android.telephony.TelephonyManager.DATA_CONNECTED
+import android.telephony.TelephonyManager.DATA_CONNECTING
+import android.telephony.TelephonyManager.DATA_DISCONNECTED
+import android.telephony.TelephonyManager.DATA_DISCONNECTING
+import android.telephony.TelephonyManager.DataState
+
+/** Internal enum representation of the telephony data connection states */
+enum class DataConnectionState(@DataState val dataState: Int) {
+    Connected(DATA_CONNECTED),
+    Connecting(DATA_CONNECTING),
+    Disconnected(DATA_DISCONNECTED),
+    Disconnecting(DATA_DISCONNECTING),
+}
+
+fun @receiver:DataState Int.toDataConnectionType(): DataConnectionState =
+    when (this) {
+        DATA_CONNECTED -> DataConnectionState.Connected
+        DATA_CONNECTING -> DataConnectionState.Connecting
+        DATA_DISCONNECTED -> DataConnectionState.Disconnected
+        DATA_DISCONNECTING -> DataConnectionState.Disconnecting
+        else -> throw IllegalArgumentException("unknown data state received")
+    }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectivityModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectivityModel.kt
new file mode 100644
index 0000000..e618905
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileConnectivityModel.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.mobile.data.model
+
+import android.net.NetworkCapabilities
+
+/** Provides information about a mobile network connection */
+data class MobileConnectivityModel(
+    /** Whether mobile is the connected transport see [NetworkCapabilities.TRANSPORT_CELLULAR] */
+    val isConnected: Boolean = false,
+    /** Whether the mobile transport is validated [NetworkCapabilities.NET_CAPABILITY_VALIDATED] */
+    val isValidated: Boolean = false,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileSubscriptionModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileSubscriptionModel.kt
index eaba0e9..6341a11 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileSubscriptionModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/model/MobileSubscriptionModel.kt
@@ -28,6 +28,7 @@
 import android.telephony.TelephonyDisplayInfo
 import android.telephony.TelephonyManager
 import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Disconnected
 
 /**
  * Data class containing all of the relevant information for a particular line of service, known as
@@ -49,14 +50,14 @@
     @IntRange(from = 0, to = 4)
     val primaryLevel: Int = CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN,
 
-    /** Comes directly from [DataConnectionStateListener.onDataConnectionStateChanged] */
-    val dataConnectionState: Int? = null,
+    /** Mapped from [DataConnectionStateListener.onDataConnectionStateChanged] */
+    val dataConnectionState: DataConnectionState = Disconnected,
 
     /** From [DataActivityListener.onDataActivity]. See [TelephonyManager] for the values */
     @DataActivityType val dataActivityDirection: Int? = null,
 
     /** From [CarrierNetworkListener.onCarrierNetworkChange] */
-    val carrierNetworkChangeActive: Boolean? = null,
+    val carrierNetworkChangeActive: Boolean = false,
 
     /**
      * From [DisplayInfoListener.onDisplayInfoChanged].
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
index 45284cf..581842b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepository.kt
@@ -16,11 +16,15 @@
 
 package com.android.systemui.statusbar.pipeline.mobile.data.repository
 
+import android.content.Context
+import android.database.ContentObserver
+import android.provider.Settings.Global
 import android.telephony.CellSignalStrength
 import android.telephony.CellSignalStrengthCdma
 import android.telephony.ServiceState
 import android.telephony.SignalStrength
 import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager
 import android.telephony.TelephonyCallback
 import android.telephony.TelephonyDisplayInfo
 import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NONE
@@ -31,7 +35,10 @@
 import com.android.systemui.statusbar.pipeline.mobile.data.model.DefaultNetworkType
 import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType
+import com.android.systemui.statusbar.pipeline.mobile.data.model.toDataConnectionType
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
+import com.android.systemui.util.settings.GlobalSettings
 import java.lang.IllegalStateException
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
@@ -40,8 +47,11 @@
 import kotlinx.coroutines.asExecutor
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.stateIn
 
@@ -62,13 +72,24 @@
      * listener + model.
      */
     val subscriptionModelFlow: Flow<MobileSubscriptionModel>
+    /** Observable tracking [TelephonyManager.isDataConnectionAllowed] */
+    val dataEnabled: StateFlow<Boolean>
+    /**
+     * True if this connection represents the default subscription per
+     * [SubscriptionManager.getDefaultDataSubscriptionId]
+     */
+    val isDefaultDataSubscription: StateFlow<Boolean>
 }
 
 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
 @OptIn(ExperimentalCoroutinesApi::class)
 class MobileConnectionRepositoryImpl(
+    private val context: Context,
     private val subId: Int,
-    telephonyManager: TelephonyManager,
+    private val telephonyManager: TelephonyManager,
+    private val globalSettings: GlobalSettings,
+    defaultDataSubId: StateFlow<Int>,
+    globalMobileDataSettingChangedEvent: Flow<Unit>,
     bgDispatcher: CoroutineDispatcher,
     logger: ConnectivityPipelineLogger,
     scope: CoroutineScope,
@@ -82,6 +103,8 @@
         }
     }
 
+    private val telephonyCallbackEvent = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
+
     override val subscriptionModelFlow: StateFlow<MobileSubscriptionModel> = run {
         var state = MobileSubscriptionModel()
         conflatedCallbackFlow {
@@ -127,7 +150,8 @@
                             dataState: Int,
                             networkType: Int
                         ) {
-                            state = state.copy(dataConnectionState = dataState)
+                            state =
+                                state.copy(dataConnectionState = dataState.toDataConnectionType())
                             trySend(state)
                         }
 
@@ -160,22 +184,75 @@
                 telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
                 awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
             }
-            .onEach { logger.logOutputChange("mobileSubscriptionModel", it.toString()) }
+            .onEach { telephonyCallbackEvent.tryEmit(Unit) }
+            .logOutputChange(logger, "MobileSubscriptionModel")
             .stateIn(scope, SharingStarted.WhileSubscribed(), state)
     }
 
+    /** Produces whenever the mobile data setting changes for this subId */
+    private val localMobileDataSettingChangedEvent: Flow<Unit> = conflatedCallbackFlow {
+        val observer =
+            object : ContentObserver(null) {
+                override fun onChange(selfChange: Boolean) {
+                    trySend(Unit)
+                }
+            }
+
+        globalSettings.registerContentObserver(
+            globalSettings.getUriFor("${Global.MOBILE_DATA}$subId"),
+            /* notifyForDescendants */ true,
+            observer
+        )
+
+        awaitClose { context.contentResolver.unregisterContentObserver(observer) }
+    }
+
+    /**
+     * There are a few cases where we will need to poll [TelephonyManager] so we can update some
+     * internal state where callbacks aren't provided. Any of those events should be merged into
+     * this flow, which can be used to trigger the polling.
+     */
+    private val telephonyPollingEvent: Flow<Unit> =
+        merge(
+            telephonyCallbackEvent,
+            localMobileDataSettingChangedEvent,
+            globalMobileDataSettingChangedEvent,
+        )
+
+    override val dataEnabled: StateFlow<Boolean> =
+        telephonyPollingEvent
+            .mapLatest { dataConnectionAllowed() }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), dataConnectionAllowed())
+
+    private fun dataConnectionAllowed(): Boolean = telephonyManager.isDataConnectionAllowed
+
+    override val isDefaultDataSubscription: StateFlow<Boolean> =
+        defaultDataSubId
+            .mapLatest { it == subId }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), defaultDataSubId.value == subId)
+
     class Factory
     @Inject
     constructor(
+        private val context: Context,
         private val telephonyManager: TelephonyManager,
         private val logger: ConnectivityPipelineLogger,
+        private val globalSettings: GlobalSettings,
         @Background private val bgDispatcher: CoroutineDispatcher,
         @Application private val scope: CoroutineScope,
     ) {
-        fun build(subId: Int): MobileConnectionRepository {
+        fun build(
+            subId: Int,
+            defaultDataSubId: StateFlow<Int>,
+            globalMobileDataSettingChangedEvent: Flow<Unit>,
+        ): MobileConnectionRepository {
             return MobileConnectionRepositoryImpl(
+                context,
                 subId,
                 telephonyManager.createForSubscriptionId(subId),
+                globalSettings,
+                defaultDataSubId,
+                globalMobileDataSettingChangedEvent,
                 bgDispatcher,
                 logger,
                 scope,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
index 0e2428a..c3c1f14 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepository.kt
@@ -16,15 +16,27 @@
 
 package com.android.systemui.statusbar.pipeline.mobile.data.repository
 
+import android.annotation.SuppressLint
 import android.content.Context
 import android.content.IntentFilter
+import android.database.ContentObserver
+import android.net.ConnectivityManager
+import android.net.ConnectivityManager.NetworkCallback
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
+import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
+import android.provider.Settings
+import android.provider.Settings.Global.MOBILE_DATA
 import android.telephony.CarrierConfigManager
 import android.telephony.SubscriptionInfo
 import android.telephony.SubscriptionManager
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
 import android.telephony.TelephonyCallback
 import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
 import android.telephony.TelephonyManager
 import androidx.annotation.VisibleForTesting
+import com.android.internal.telephony.PhoneConstants
 import com.android.settingslib.mobile.MobileMappings
 import com.android.settingslib.mobile.MobileMappings.Config
 import com.android.systemui.broadcast.BroadcastDispatcher
@@ -32,7 +44,9 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
+import com.android.systemui.util.settings.GlobalSettings
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
@@ -40,10 +54,12 @@
 import kotlinx.coroutines.asExecutor
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.withContext
@@ -57,13 +73,22 @@
     val subscriptionsFlow: Flow<List<SubscriptionInfo>>
 
     /** Observable for the subscriptionId of the current mobile data connection */
-    val activeMobileDataSubscriptionId: Flow<Int>
+    val activeMobileDataSubscriptionId: StateFlow<Int>
 
     /** Observable for [MobileMappings.Config] tracking the defaults */
     val defaultDataSubRatConfig: StateFlow<Config>
 
+    /** Tracks [SubscriptionManager.getDefaultDataSubscriptionId] */
+    val defaultDataSubId: StateFlow<Int>
+
+    /** The current connectivity status for the default mobile network connection */
+    val defaultMobileNetworkConnectivity: StateFlow<MobileConnectivityModel>
+
     /** Get or create a repository for the line of service for the given subscription ID */
     fun getRepoForSubId(subId: Int): MobileConnectionRepository
+
+    /** Observe changes to the [Settings.Global.MOBILE_DATA] setting */
+    val globalMobileDataSettingChangedEvent: Flow<Unit>
 }
 
 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
@@ -72,10 +97,12 @@
 class MobileConnectionsRepositoryImpl
 @Inject
 constructor(
+    private val connectivityManager: ConnectivityManager,
     private val subscriptionManager: SubscriptionManager,
     private val telephonyManager: TelephonyManager,
     private val logger: ConnectivityPipelineLogger,
     broadcastDispatcher: BroadcastDispatcher,
+    private val globalSettings: GlobalSettings,
     private val context: Context,
     @Background private val bgDispatcher: CoroutineDispatcher,
     @Application private val scope: CoroutineScope,
@@ -121,17 +148,26 @@
                 telephonyManager.registerTelephonyCallback(bgDispatcher.asExecutor(), callback)
                 awaitClose { telephonyManager.unregisterTelephonyCallback(callback) }
             }
+            .stateIn(scope, started = SharingStarted.WhileSubscribed(), INVALID_SUBSCRIPTION_ID)
+
+    private val defaultDataSubIdChangeEvent: MutableSharedFlow<Unit> =
+        MutableSharedFlow(extraBufferCapacity = 1)
+
+    override val defaultDataSubId: StateFlow<Int> =
+        broadcastDispatcher
+            .broadcastFlow(
+                IntentFilter(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
+            ) { intent, _ ->
+                intent.getIntExtra(PhoneConstants.SUBSCRIPTION_KEY, INVALID_SUBSCRIPTION_ID)
+            }
+            .distinctUntilChanged()
+            .onEach { defaultDataSubIdChangeEvent.tryEmit(Unit) }
             .stateIn(
                 scope,
-                started = SharingStarted.WhileSubscribed(),
-                SubscriptionManager.INVALID_SUBSCRIPTION_ID
+                SharingStarted.WhileSubscribed(),
+                SubscriptionManager.getDefaultDataSubscriptionId()
             )
 
-    private val defaultDataSubChangedEvent =
-        broadcastDispatcher.broadcastFlow(
-            IntentFilter(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
-        )
-
     private val carrierConfigChangedEvent =
         broadcastDispatcher.broadcastFlow(
             IntentFilter(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED)
@@ -148,9 +184,8 @@
      * This flow will produce whenever the default data subscription or the carrier config changes.
      */
     override val defaultDataSubRatConfig: StateFlow<Config> =
-        combine(defaultDataSubChangedEvent, carrierConfigChangedEvent) { _, _ ->
-                Config.readConfig(context)
-            }
+        merge(defaultDataSubIdChangeEvent, carrierConfigChangedEvent)
+            .mapLatest { Config.readConfig(context) }
             .stateIn(
                 scope,
                 SharingStarted.WhileSubscribed(),
@@ -168,6 +203,57 @@
             ?: createRepositoryForSubId(subId).also { subIdRepositoryCache[subId] = it }
     }
 
+    /**
+     * In single-SIM devices, the [MOBILE_DATA] setting is phone-wide. For multi-SIM, the individual
+     * connection repositories also observe the URI for [MOBILE_DATA] + subId.
+     */
+    override val globalMobileDataSettingChangedEvent: Flow<Unit> = conflatedCallbackFlow {
+        val observer =
+            object : ContentObserver(null) {
+                override fun onChange(selfChange: Boolean) {
+                    trySend(Unit)
+                }
+            }
+
+        globalSettings.registerContentObserver(
+            globalSettings.getUriFor(MOBILE_DATA),
+            true,
+            observer
+        )
+
+        awaitClose { context.contentResolver.unregisterContentObserver(observer) }
+    }
+
+    @SuppressLint("MissingPermission")
+    override val defaultMobileNetworkConnectivity: StateFlow<MobileConnectivityModel> =
+        conflatedCallbackFlow {
+                val callback =
+                    object : NetworkCallback(FLAG_INCLUDE_LOCATION_INFO) {
+                        override fun onLost(network: Network) {
+                            // Send a disconnected model when lost. Maybe should create a sealed
+                            // type or null here?
+                            trySend(MobileConnectivityModel())
+                        }
+
+                        override fun onCapabilitiesChanged(
+                            network: Network,
+                            caps: NetworkCapabilities
+                        ) {
+                            trySend(
+                                MobileConnectivityModel(
+                                    isConnected = caps.hasTransport(TRANSPORT_CELLULAR),
+                                    isValidated = caps.hasCapability(NET_CAPABILITY_VALIDATED),
+                                )
+                            )
+                        }
+                    }
+
+                connectivityManager.registerDefaultNetworkCallback(callback)
+
+                awaitClose { connectivityManager.unregisterNetworkCallback(callback) }
+            }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), MobileConnectivityModel())
+
     private fun isValidSubId(subId: Int): Boolean {
         subscriptionsFlow.value.forEach {
             if (it.subscriptionId == subId) {
@@ -181,7 +267,11 @@
     @VisibleForTesting fun getSubIdRepoCache() = subIdRepositoryCache
 
     private fun createRepositoryForSubId(subId: Int): MobileConnectionRepository {
-        return mobileConnectionRepositoryFactory.build(subId)
+        return mobileConnectionRepositoryFactory.build(
+            subId,
+            defaultDataSubId,
+            globalMobileDataSettingChangedEvent,
+        )
     }
 
     private fun dropUnusedReposFromCache(newInfos: List<SubscriptionInfo>) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepository.kt
index 77de849..91886bb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/UserSetupRepository.kt
@@ -26,7 +26,6 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.channels.awaitClose
-import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.mapLatest
@@ -40,7 +39,7 @@
  */
 interface UserSetupRepository {
     /** Observable tracking [DeviceProvisionedController.isUserSetup] */
-    val isUserSetupFlow: Flow<Boolean>
+    val isUserSetupFlow: StateFlow<Boolean>
 }
 
 @Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
index 15f4acc..0da84f0 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractor.kt
@@ -18,76 +18,109 @@
 
 import android.telephony.CarrierConfigManager
 import com.android.settingslib.SignalIcon.MobileIconGroup
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState.Connected
 import com.android.systemui.statusbar.pipeline.mobile.data.model.DefaultNetworkType
 import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
 import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
 import com.android.systemui.util.CarrierConfigTracker
-import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapLatest
+import kotlinx.coroutines.flow.stateIn
 
 interface MobileIconInteractor {
+    /** Only true if mobile is the default transport but is not validated, otherwise false */
+    val isDefaultConnectionFailed: StateFlow<Boolean>
+
+    /** True when telephony tells us that the data state is CONNECTED */
+    val isDataConnected: StateFlow<Boolean>
+
+    // TODO(b/256839546): clarify naming of default vs active
+    /** True if we want to consider the data connection enabled */
+    val isDefaultDataEnabled: StateFlow<Boolean>
+
+    /** Observable for the data enabled state of this connection */
+    val isDataEnabled: StateFlow<Boolean>
+
     /** Observable for RAT type (network type) indicator */
-    val networkTypeIconGroup: Flow<MobileIconGroup>
+    val networkTypeIconGroup: StateFlow<MobileIconGroup>
 
     /** True if this line of service is emergency-only */
-    val isEmergencyOnly: Flow<Boolean>
+    val isEmergencyOnly: StateFlow<Boolean>
 
     /** Int describing the connection strength. 0-4 OR 1-5. See [numberOfLevels] */
-    val level: Flow<Int>
+    val level: StateFlow<Int>
 
     /** Based on [CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL], either 4 or 5 */
-    val numberOfLevels: Flow<Int>
-
-    /** True when we want to draw an icon that makes room for the exclamation mark */
-    val cutOut: Flow<Boolean>
+    val numberOfLevels: StateFlow<Int>
 }
 
 /** Interactor for a single mobile connection. This connection _should_ have one subscription ID */
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
 class MobileIconInteractorImpl(
-    defaultMobileIconMapping: Flow<Map<String, MobileIconGroup>>,
-    defaultMobileIconGroup: Flow<MobileIconGroup>,
+    @Application scope: CoroutineScope,
+    defaultSubscriptionHasDataEnabled: StateFlow<Boolean>,
+    defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>>,
+    defaultMobileIconGroup: StateFlow<MobileIconGroup>,
+    override val isDefaultConnectionFailed: StateFlow<Boolean>,
     mobileMappingsProxy: MobileMappingsProxy,
     connectionRepository: MobileConnectionRepository,
 ) : MobileIconInteractor {
     private val mobileStatusInfo = connectionRepository.subscriptionModelFlow
 
+    override val isDataEnabled: StateFlow<Boolean> = connectionRepository.dataEnabled
+
+    override val isDefaultDataEnabled = defaultSubscriptionHasDataEnabled
+
     /** Observable for the current RAT indicator icon ([MobileIconGroup]) */
-    override val networkTypeIconGroup: Flow<MobileIconGroup> =
+    override val networkTypeIconGroup: StateFlow<MobileIconGroup> =
         combine(
-            mobileStatusInfo,
-            defaultMobileIconMapping,
-            defaultMobileIconGroup,
-        ) { info, mapping, defaultGroup ->
-            val lookupKey =
-                when (val resolved = info.resolvedNetworkType) {
-                    is DefaultNetworkType -> mobileMappingsProxy.toIconKey(resolved.type)
-                    is OverrideNetworkType -> mobileMappingsProxy.toIconKeyOverride(resolved.type)
-                }
-            mapping[lookupKey] ?: defaultGroup
-        }
-
-    override val isEmergencyOnly: Flow<Boolean> = mobileStatusInfo.map { it.isEmergencyOnly }
-
-    override val level: Flow<Int> =
-        mobileStatusInfo.map { mobileModel ->
-            // TODO: incorporate [MobileMappings.Config.alwaysShowCdmaRssi]
-            if (mobileModel.isGsm) {
-                mobileModel.primaryLevel
-            } else {
-                mobileModel.cdmaLevel
+                mobileStatusInfo,
+                defaultMobileIconMapping,
+                defaultMobileIconGroup,
+            ) { info, mapping, defaultGroup ->
+                val lookupKey =
+                    when (val resolved = info.resolvedNetworkType) {
+                        is DefaultNetworkType -> mobileMappingsProxy.toIconKey(resolved.type)
+                        is OverrideNetworkType ->
+                            mobileMappingsProxy.toIconKeyOverride(resolved.type)
+                    }
+                mapping[lookupKey] ?: defaultGroup
             }
-        }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), defaultMobileIconGroup.value)
+
+    override val isEmergencyOnly: StateFlow<Boolean> =
+        mobileStatusInfo
+            .mapLatest { it.isEmergencyOnly }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+    override val level: StateFlow<Int> =
+        mobileStatusInfo
+            .mapLatest { mobileModel ->
+                // TODO: incorporate [MobileMappings.Config.alwaysShowCdmaRssi]
+                if (mobileModel.isGsm) {
+                    mobileModel.primaryLevel
+                } else {
+                    mobileModel.cdmaLevel
+                }
+            }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), 0)
 
     /**
      * This will become variable based on [CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL]
      * once it's wired up inside of [CarrierConfigTracker]
      */
-    override val numberOfLevels: Flow<Int> = flowOf(4)
+    override val numberOfLevels: StateFlow<Int> = MutableStateFlow(4)
 
-    /** Whether or not to draw the mobile triangle as "cut out", i.e., with the exclamation mark */
-    // TODO: find a better name for this?
-    override val cutOut: Flow<Boolean> = flowOf(false)
+    override val isDataConnected: StateFlow<Boolean> =
+        mobileStatusInfo
+            .mapLatest { subscriptionModel -> subscriptionModel.dataConnectionState == Connected }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
index cd411a4..a4175c3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractor.kt
@@ -19,21 +19,26 @@
 import android.telephony.CarrierConfigManager
 import android.telephony.SubscriptionInfo
 import android.telephony.SubscriptionManager
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
 import com.android.settingslib.SignalIcon.MobileIconGroup
 import com.android.settingslib.mobile.TelephonyIcons
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.UserSetupRepository
 import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
 import com.android.systemui.util.CarrierConfigTracker
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.mapLatest
 import kotlinx.coroutines.flow.stateIn
 
 /**
@@ -47,28 +52,58 @@
  * icon
  */
 interface MobileIconsInteractor {
+    /** List of subscriptions, potentially filtered for CBRS */
     val filteredSubscriptions: Flow<List<SubscriptionInfo>>
-    val defaultMobileIconMapping: Flow<Map<String, MobileIconGroup>>
-    val defaultMobileIconGroup: Flow<MobileIconGroup>
-    val isUserSetup: Flow<Boolean>
+    /** True if the active mobile data subscription has data enabled */
+    val activeDataConnectionHasDataEnabled: StateFlow<Boolean>
+    /** The icon mapping from network type to [MobileIconGroup] for the default subscription */
+    val defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>>
+    /** Fallback [MobileIconGroup] in the case where there is no icon in the mapping */
+    val defaultMobileIconGroup: StateFlow<MobileIconGroup>
+    /** True only if the default network is mobile, and validation also failed */
+    val isDefaultConnectionFailed: StateFlow<Boolean>
+    /** True once the user has been set up */
+    val isUserSetup: StateFlow<Boolean>
+    /**
+     * Vends out a [MobileIconInteractor] tracking the [MobileConnectionRepository] for the given
+     * subId. Will throw if the ID is invalid
+     */
     fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor
 }
 
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
 class MobileIconsInteractorImpl
 @Inject
 constructor(
-    private val mobileSubscriptionRepo: MobileConnectionsRepository,
+    private val mobileConnectionsRepo: MobileConnectionsRepository,
     private val carrierConfigTracker: CarrierConfigTracker,
     private val mobileMappingsProxy: MobileMappingsProxy,
     userSetupRepo: UserSetupRepository,
     @Application private val scope: CoroutineScope,
 ) : MobileIconsInteractor {
     private val activeMobileDataSubscriptionId =
-        mobileSubscriptionRepo.activeMobileDataSubscriptionId
+        mobileConnectionsRepo.activeMobileDataSubscriptionId
+
+    private val activeMobileDataConnectionRepo: StateFlow<MobileConnectionRepository?> =
+        activeMobileDataSubscriptionId
+            .mapLatest { activeId ->
+                if (activeId == INVALID_SUBSCRIPTION_ID) {
+                    null
+                } else {
+                    mobileConnectionsRepo.getRepoForSubId(activeId)
+                }
+            }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), null)
+
+    override val activeDataConnectionHasDataEnabled: StateFlow<Boolean> =
+        activeMobileDataConnectionRepo
+            .flatMapLatest { it?.dataEnabled ?: flowOf(false) }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), false)
 
     private val unfilteredSubscriptions: Flow<List<SubscriptionInfo>> =
-        mobileSubscriptionRepo.subscriptionsFlow
+        mobileConnectionsRepo.subscriptionsFlow
 
     /**
      * Generally, SystemUI wants to show iconography for each subscription that is listed by
@@ -119,24 +154,42 @@
      * subscription Id. This mapping is the same for every subscription.
      */
     override val defaultMobileIconMapping: StateFlow<Map<String, MobileIconGroup>> =
-        mobileSubscriptionRepo.defaultDataSubRatConfig
-            .map { mobileMappingsProxy.mapIconSets(it) }
+        mobileConnectionsRepo.defaultDataSubRatConfig
+            .mapLatest { mobileMappingsProxy.mapIconSets(it) }
             .stateIn(scope, SharingStarted.WhileSubscribed(), initialValue = mapOf())
 
     /** If there is no mapping in [defaultMobileIconMapping], then use this default icon group */
     override val defaultMobileIconGroup: StateFlow<MobileIconGroup> =
-        mobileSubscriptionRepo.defaultDataSubRatConfig
-            .map { mobileMappingsProxy.getDefaultIcons(it) }
+        mobileConnectionsRepo.defaultDataSubRatConfig
+            .mapLatest { mobileMappingsProxy.getDefaultIcons(it) }
             .stateIn(scope, SharingStarted.WhileSubscribed(), initialValue = TelephonyIcons.G)
 
-    override val isUserSetup: Flow<Boolean> = userSetupRepo.isUserSetupFlow
+    /**
+     * We want to show an error state when cellular has actually failed to validate, but not if some
+     * other transport type is active, because then we expect there not to be validation.
+     */
+    override val isDefaultConnectionFailed: StateFlow<Boolean> =
+        mobileConnectionsRepo.defaultMobileNetworkConnectivity
+            .mapLatest { connectivityModel ->
+                if (!connectivityModel.isConnected) {
+                    false
+                } else {
+                    !connectivityModel.isValidated
+                }
+            }
+            .stateIn(scope, SharingStarted.WhileSubscribed(), false)
+
+    override val isUserSetup: StateFlow<Boolean> = userSetupRepo.isUserSetupFlow
 
     /** Vends out new [MobileIconInteractor] for a particular subId */
     override fun createMobileConnectionInteractorForSubId(subId: Int): MobileIconInteractor =
         MobileIconInteractorImpl(
+            scope,
+            activeDataConnectionHasDataEnabled,
             defaultMobileIconMapping,
             defaultMobileIconGroup,
+            isDefaultConnectionFailed,
             mobileMappingsProxy,
-            mobileSubscriptionRepo.getRepoForSubId(subId),
+            mobileConnectionsRepo.getRepoForSubId(subId),
         )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
index 380017c..c7e0ce1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/MobileUiAdapter.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.statusbar.phone.StatusBarIconController
 import com.android.systemui.statusbar.phone.StatusBarIconController.IconManager
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
 import com.android.systemui.statusbar.pipeline.mobile.ui.viewmodel.MobileIconsViewModel
 import javax.inject.Inject
@@ -50,6 +51,7 @@
     private val iconController: StatusBarIconController,
     private val iconsViewModelFactory: MobileIconsViewModel.Factory,
     @Application scope: CoroutineScope,
+    private val statusBarPipelineFlags: StatusBarPipelineFlags,
 ) {
     private val mobileSubIds: Flow<List<Int>> =
         interactor.filteredSubscriptions.mapLatest { infos ->
@@ -66,8 +68,14 @@
     private val mobileSubIdsState: StateFlow<List<Int>> =
         mobileSubIds
             .onEach {
-                // Notify the icon controller here so that it knows to add icons
-                iconController.setNewMobileIconSubIds(it)
+                // Only notify the icon controller if we want to *render* the new icons.
+                // Note that this flow may still run if
+                // [statusBarPipelineFlags.runNewMobileIconsBackend] is true because we may want to
+                // get the logging data without rendering.
+                if (statusBarPipelineFlags.useNewMobileIcons()) {
+                    // Notify the icon controller here so that it knows to add icons
+                    iconController.setNewMobileIconSubIds(it)
+                }
             }
             .stateIn(scope, SharingStarted.WhileSubscribed(), listOf())
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
index cc8f6dd..7869021 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModel.kt
@@ -24,11 +24,12 @@
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.MobileIconsInteractor
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger.Companion.logOutputChange
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapLatest
 
 /**
  * View model for the state of a single mobile icon. Each [MobileIconViewModel] will keep watch over
@@ -40,32 +41,47 @@
  *
  * TODO: figure out where carrier merged and VCN models go (probably here?)
  */
+@Suppress("EXPERIMENTAL_IS_NOT_ENABLED")
+@OptIn(ExperimentalCoroutinesApi::class)
 class MobileIconViewModel
 constructor(
     val subscriptionId: Int,
     iconInteractor: MobileIconInteractor,
     logger: ConnectivityPipelineLogger,
 ) {
+    /** Whether or not to show the error state of [SignalDrawable] */
+    private val showExclamationMark: Flow<Boolean> =
+        iconInteractor.isDefaultDataEnabled.mapLatest { !it }
+
     /** An int consumable by [SignalDrawable] for display */
-    var iconId: Flow<Int> =
-        combine(iconInteractor.level, iconInteractor.numberOfLevels, iconInteractor.cutOut) {
+    val iconId: Flow<Int> =
+        combine(iconInteractor.level, iconInteractor.numberOfLevels, showExclamationMark) {
                 level,
                 numberOfLevels,
-                cutOut ->
-                SignalDrawable.getState(level, numberOfLevels, cutOut)
+                showExclamationMark ->
+                SignalDrawable.getState(level, numberOfLevels, showExclamationMark)
             }
             .distinctUntilChanged()
             .logOutputChange(logger, "iconId($subscriptionId)")
 
     /** The RAT icon (LTE, 3G, 5G, etc) to be displayed. Null if we shouldn't show anything */
-    var networkTypeIcon: Flow<Icon?> =
-        iconInteractor.networkTypeIconGroup.map {
-            val desc =
-                if (it.dataContentDescription != 0)
-                    ContentDescription.Resource(it.dataContentDescription)
-                else null
-            Icon.Resource(it.dataType, desc)
+    val networkTypeIcon: Flow<Icon?> =
+        combine(
+            iconInteractor.networkTypeIconGroup,
+            iconInteractor.isDataConnected,
+            iconInteractor.isDataEnabled,
+            iconInteractor.isDefaultConnectionFailed,
+        ) { networkTypeIconGroup, dataConnected, dataEnabled, failedConnection ->
+            if (!dataConnected || !dataEnabled || failedConnection) {
+                null
+            } else {
+                val desc =
+                    if (networkTypeIconGroup.dataContentDescription != 0)
+                        ContentDescription.Resource(networkTypeIconGroup.dataContentDescription)
+                    else null
+                Icon.Resource(networkTypeIconGroup.dataType, desc)
+            }
         }
 
-    var tint: Flow<Int> = flowOf(Color.CYAN)
+    val tint: Flow<Int> = flowOf(Color.CYAN)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityConstants.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityConstants.kt
index 118b94c7..6efb10f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityConstants.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/ConnectivityConstants.kt
@@ -34,7 +34,7 @@
 @Inject
 constructor(dumpManager: DumpManager, telephonyManager: TelephonyManager) : Dumpable {
     init {
-        dumpManager.registerDumpable("$SB_LOGGING_TAG:ConnectivityConstants", this)
+        dumpManager.registerDumpable("${SB_LOGGING_TAG}Constants", this)
     }
 
     /** True if this device has the capability for data connections and false otherwise. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt
index 6b1750d..45c6d46 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/shared/data/repository/ConnectivityRepository.kt
@@ -62,7 +62,7 @@
     tunerService: TunerService,
 ) : ConnectivityRepository, Dumpable {
     init {
-        dumpManager.registerDumpable("$SB_LOGGING_TAG:ConnectivityRepository", this)
+        dumpManager.registerDumpable("${SB_LOGGING_TAG}Repository", this)
     }
 
     // The default set of hidden icons to use if we don't get any from [TunerService].
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt
index 0eb4b0d..3c0eb91 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/shared/WifiConstants.kt
@@ -35,7 +35,7 @@
         dumpManager: DumpManager,
 ) : Dumpable {
     init {
-        dumpManager.registerDumpable("$SB_LOGGING_TAG:WifiConstants", this)
+        dumpManager.registerDumpable("${SB_LOGGING_TAG}WifiConstants", this)
     }
 
     /** True if we should show the activityIn/activityOut icons and false otherwise. */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/WifiUiAdapter.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/WifiUiAdapter.kt
new file mode 100644
index 0000000..b816364
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/WifiUiAdapter.kt
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.pipeline.wifi.ui
+
+import android.view.ViewGroup
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.statusbar.phone.StatusBarIconController
+import com.android.systemui.statusbar.phone.StatusBarLocation
+import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
+import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
+import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
+import javax.inject.Inject
+import kotlinx.coroutines.flow.collect
+import kotlinx.coroutines.launch
+
+/**
+ * This class serves as a bridge between the old UI classes and the new data pipeline.
+ *
+ * Once the new pipeline notifies [wifiViewModel] that the wifi icon should be visible, this class
+ * notifies [iconController] to inflate the wifi icon (if needed). After that, the [wifiViewModel]
+ * has sole responsibility for updating the wifi icon drawable, visibility, etc. and the
+ * [iconController] will not do any updates to the icon.
+ */
+@SysUISingleton
+class WifiUiAdapter
+@Inject
+constructor(
+    private val iconController: StatusBarIconController,
+    private val wifiViewModel: WifiViewModel,
+    private val statusBarPipelineFlags: StatusBarPipelineFlags,
+) {
+    /**
+     * Binds the container for all the status bar icons to a view model, so that we inflate the wifi
+     * view once we receive a valid icon from the data pipeline.
+     *
+     * NOTE: This should go away as we better integrate the data pipeline with the UI.
+     *
+     * @return the view model used for this particular group in the given [location].
+     */
+    fun bindGroup(
+        statusBarIconGroup: ViewGroup,
+        location: StatusBarLocation,
+    ): LocationBasedWifiViewModel {
+        val locationViewModel =
+            when (location) {
+                StatusBarLocation.HOME -> wifiViewModel.home
+                StatusBarLocation.KEYGUARD -> wifiViewModel.keyguard
+                StatusBarLocation.QS -> wifiViewModel.qs
+            }
+
+        statusBarIconGroup.repeatWhenAttached {
+            repeatOnLifecycle(Lifecycle.State.STARTED) {
+                launch {
+                    locationViewModel.wifiIcon.collect { wifiIcon ->
+                        // Only notify the icon controller if we want to *render* the new icon.
+                        // Note that this flow may still run if
+                        // [statusBarPipelineFlags.runNewWifiIconBackend] is true because we may
+                        // want to get the logging data without rendering.
+                        if (wifiIcon != null && statusBarPipelineFlags.useNewWifiIcon()) {
+                            iconController.setNewWifiIcon()
+                        }
+                    }
+                }
+            }
+        }
+
+        return locationViewModel
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
index 25537b9..345f8cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/binder/WifiViewBinder.kt
@@ -30,9 +30,7 @@
 import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
 import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
 import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
-import com.android.systemui.statusbar.phone.StatusBarLocation
 import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
-import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
 import kotlinx.coroutines.InternalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.collect
@@ -62,26 +60,9 @@
         fun onVisibilityStateChanged(@StatusBarIconView.VisibleState state: Int)
     }
 
-    /**
-     * Binds the view to the appropriate view-model based on the given location. The view will
-     * continue to be updated following updates from the view-model.
-     */
-    @JvmStatic
-    fun bind(
-        view: ViewGroup,
-        wifiViewModel: WifiViewModel,
-        location: StatusBarLocation,
-    ): Binding {
-        return when (location) {
-            StatusBarLocation.HOME -> bind(view, wifiViewModel.home)
-            StatusBarLocation.KEYGUARD -> bind(view, wifiViewModel.keyguard)
-            StatusBarLocation.QS -> bind(view, wifiViewModel.qs)
-        }
-    }
-
     /** Binds the view to the view-model, continuing to update the former based on the latter. */
     @JvmStatic
-    private fun bind(
+    fun bind(
         view: ViewGroup,
         viewModel: LocationBasedWifiViewModel,
     ): Binding {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
index 0cd9bd7..a45076b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiView.kt
@@ -26,9 +26,8 @@
 import com.android.systemui.statusbar.StatusBarIconView
 import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
 import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
-import com.android.systemui.statusbar.phone.StatusBarLocation
 import com.android.systemui.statusbar.pipeline.wifi.ui.binder.WifiViewBinder
-import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
+import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
 
 /**
  * A new and more modern implementation of [com.android.systemui.statusbar.StatusBarWifiView] that
@@ -81,12 +80,11 @@
 
     private fun initView(
         slotName: String,
-        wifiViewModel: WifiViewModel,
-        location: StatusBarLocation,
+        wifiViewModel: LocationBasedWifiViewModel,
     ) {
         slot = slotName
         initDotView()
-        binding = WifiViewBinder.bind(this, wifiViewModel, location)
+        binding = WifiViewBinder.bind(this, wifiViewModel)
     }
 
     // Mostly duplicated from [com.android.systemui.statusbar.StatusBarWifiView].
@@ -116,14 +114,13 @@
         fun constructAndBind(
             context: Context,
             slot: String,
-            wifiViewModel: WifiViewModel,
-            location: StatusBarLocation,
+            wifiViewModel: LocationBasedWifiViewModel,
         ): ModernStatusBarWifiView {
             return (
                 LayoutInflater.from(context).inflate(R.layout.new_status_bar_wifi_group, null)
                     as ModernStatusBarWifiView
                 ).also {
-                    it.initView(slot, wifiViewModel, location)
+                    it.initView(slot, wifiViewModel)
                 }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
index 89b96b7..0782bbb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/ui/viewmodel/WifiViewModel.kt
@@ -145,7 +145,8 @@
                 else -> null
             }
         }
-        .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = null)
+                .logOutputChange(logger, "icon") { icon -> icon?.contentDescription.toString() }
+                .stateIn(scope, started = SharingStarted.WhileSubscribed(), initialValue = null)
 
     /** The wifi activity status. Null if we shouldn't display the activity status. */
     private val activity: Flow<WifiActivityModel?> =
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
index bc2ae64..e326611 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
@@ -67,7 +67,7 @@
         internal const val QS_DEFAULT_POSITION = 7
 
         internal const val PREFS_CONTROLS_SEEDING_COMPLETED = "SeedingCompleted"
-        internal const val PREFS_CONTROLS_FILE = "controls_prefs"
+        const val PREFS_CONTROLS_FILE = "controls_prefs"
         internal const val PREFS_SETTINGS_DIALOG_ATTEMPTS = "show_settings_attempts"
         private const val SEEDING_MAX = 2
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
index 1d414745..7acdaff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceStateRotationLockSettingController.java
@@ -118,7 +118,10 @@
 
     private void updateDeviceState(int state) {
         Log.v(TAG, "updateDeviceState [state=" + state + "]");
-        Trace.beginSection("updateDeviceState [state=" + state + "]");
+        if (Trace.isEnabled()) {
+            Trace.traceBegin(
+                    Trace.TRACE_TAG_APP, "updateDeviceState [state=" + state + "]");
+        }
         try {
             if (mDeviceState == state) {
                 return;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
index bd2123a..69b55c8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HotspotControllerImpl.java
@@ -33,6 +33,7 @@
 import androidx.annotation.NonNull;
 
 import com.android.internal.util.ConcurrentUtils;
+import com.android.systemui.R;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.dagger.qualifiers.Main;
@@ -63,6 +64,7 @@
     private volatile int mNumConnectedDevices;
     // Assume tethering is available until told otherwise
     private volatile boolean mIsTetheringSupported = true;
+    private final boolean mIsTetheringSupportedConfig;
     private volatile boolean mHasTetherableWifiRegexs = true;
     private boolean mWaitingForTerminalState;
 
@@ -100,23 +102,29 @@
         mTetheringManager = context.getSystemService(TetheringManager.class);
         mWifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
         mMainHandler = mainHandler;
-        mTetheringManager.registerTetheringEventCallback(
-                new HandlerExecutor(backgroundHandler), mTetheringCallback);
+        mIsTetheringSupportedConfig = context.getResources()
+                .getBoolean(R.bool.config_show_wifi_tethering);
+        if (mIsTetheringSupportedConfig) {
+            mTetheringManager.registerTetheringEventCallback(
+                    new HandlerExecutor(backgroundHandler), mTetheringCallback);
+        }
         dumpManager.registerDumpable(getClass().getSimpleName(), this);
     }
 
     /**
      * Whether hotspot is currently supported.
      *
-     * This will return {@code true} immediately on creation of the controller, but may be updated
-     * later. Callbacks from this controllers will notify if the state changes.
+     * This may return {@code true} immediately on creation of the controller, but may be updated
+     * later as capabilities are collected from System Server.
+     *
+     * Callbacks from this controllers will notify if the state changes.
      *
      * @return {@code true} if hotspot is supported (or we haven't been told it's not)
      * @see #addCallback
      */
     @Override
     public boolean isHotspotSupported() {
-        return mIsTetheringSupported && mHasTetherableWifiRegexs
+        return mIsTetheringSupportedConfig && mIsTetheringSupported && mHasTetherableWifiRegexs
                 && UserManager.get(mContext).isUserAdmin(ActivityManager.getCurrentUser());
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
index f0a50de..637fac0 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayController.kt
@@ -44,11 +44,6 @@
  *
  * The generic type T is expected to contain all the information necessary for the subclasses to
  * display the view in a certain state, since they receive <T> in [updateView].
- *
- * @property windowTitle the title to use for the window that displays the temporary view. Should be
- *   normally cased, like "Window Title".
- * @property wakeReason a string used for logging if we needed to wake the screen in order to
- *   display the temporary view. Should be screaming snake cased, like WAKE_REASON.
  */
 abstract class TemporaryViewDisplayController<T : TemporaryViewInfo, U : TemporaryViewLogger>(
     internal val context: Context,
@@ -59,8 +54,6 @@
     private val configurationController: ConfigurationController,
     private val powerManager: PowerManager,
     @LayoutRes private val viewLayoutRes: Int,
-    private val windowTitle: String,
-    private val wakeReason: String,
 ) : CoreStartable {
     /**
      * Window layout params that will be used as a starting point for the [windowLayoutParams] of
@@ -72,7 +65,6 @@
         type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR
         flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
             WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
-        title = windowTitle
         format = PixelFormat.TRANSLUCENT
         setTrustedOverlay()
     }
@@ -100,29 +92,40 @@
     fun displayView(newInfo: T) {
         val currentDisplayInfo = displayInfo
 
-        if (currentDisplayInfo != null) {
+        if (currentDisplayInfo != null &&
+            currentDisplayInfo.info.windowTitle == newInfo.windowTitle) {
+            // We're already displaying information in the correctly-titled window, so we just need
+            // to update the view.
             currentDisplayInfo.info = newInfo
             updateView(currentDisplayInfo.info, currentDisplayInfo.view)
         } else {
-            // The view is new, so set up all our callbacks and inflate the view
+            if (currentDisplayInfo != null) {
+                // We're already displaying information but that information is under a different
+                // window title. So, we need to remove the old window with the old title and add a
+                // new window with the new title.
+                removeView(removalReason = "New info has new window title: ${newInfo.windowTitle}")
+            }
+
+            // At this point, we're guaranteed to no longer be displaying a view.
+            // So, set up all our callbacks and inflate the view.
             configurationController.addCallback(displayScaleListener)
             // Wake the screen if necessary so the user will see the view. (Per b/239426653, we want
             // the view to show over the dream state, so we should only wake up if the screen is
             // completely off.)
             if (!powerManager.isScreenOn) {
                 powerManager.wakeUp(
-                        SystemClock.uptimeMillis(),
-                        PowerManager.WAKE_REASON_APPLICATION,
-                        "com.android.systemui:$wakeReason",
+                    SystemClock.uptimeMillis(),
+                    PowerManager.WAKE_REASON_APPLICATION,
+                    "com.android.systemui:${newInfo.wakeReason}",
                 )
             }
-            logger.logChipAddition()
+            logger.logViewAddition(newInfo.windowTitle)
             inflateAndUpdateView(newInfo)
         }
 
         // Cancel and re-set the view timeout each time we get a new state.
         val timeout = accessibilityManager.getRecommendedTimeoutMillis(
-            newInfo.getTimeoutMs().toInt(),
+            newInfo.timeoutMs,
             // Not all views have controls so FLAG_CONTENT_CONTROLS might be superfluous, but
             // include it just to be safe.
             FLAG_CONTENT_ICONS or FLAG_CONTENT_TEXT or FLAG_CONTENT_CONTROLS
@@ -147,7 +150,12 @@
         val newDisplayInfo = DisplayInfo(newView, newInfo)
         displayInfo = newDisplayInfo
         updateView(newDisplayInfo.info, newDisplayInfo.view)
-        windowManager.addView(newView, windowLayoutParams)
+
+        val paramsWithTitle = WindowManager.LayoutParams().also {
+            it.copyFrom(windowLayoutParams)
+            it.title = newInfo.windowTitle
+        }
+        windowManager.addView(newView, paramsWithTitle)
         animateViewIn(newView)
     }
 
@@ -177,7 +185,7 @@
         val currentView = currentDisplayInfo.view
         animateViewOut(currentView) { windowManager.removeView(currentView) }
 
-        logger.logChipRemoval(removalReason)
+        logger.logViewRemoval(removalReason)
         configurationController.removeCallback(displayScaleListener)
         // Re-set to null immediately (instead as part of the animation end runnable) so
         // that if a new view event comes in while this view is animating out, we still display the
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt
index 4fe753a..cbb5002 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewInfo.kt
@@ -19,12 +19,24 @@
 /**
  * A superclass view state used with [TemporaryViewDisplayController].
  */
-interface TemporaryViewInfo {
+abstract class TemporaryViewInfo {
     /**
-     * Returns the amount of time the given view state should display on the screen before it times
-     * out and disappears.
+     * The title to use for the window that displays the temporary view. Should be normally cased,
+     * like "Window Title".
      */
-    fun getTimeoutMs(): Long = DEFAULT_TIMEOUT_MILLIS
+    abstract val windowTitle: String
+
+    /**
+     * A string used for logging if we needed to wake the screen in order to display the temporary
+     * view. Should be screaming snake cased, like WAKE_REASON.
+     */
+    abstract val wakeReason: String
+
+    /**
+     * The amount of time the given view state should display on the screen before it times out and
+     * disappears.
+     */
+    open val timeoutMs: Int = DEFAULT_TIMEOUT_MILLIS
 }
 
-const val DEFAULT_TIMEOUT_MILLIS = 10000L
+const val DEFAULT_TIMEOUT_MILLIS = 10000
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
index a7185cb..428a104 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/TemporaryViewLogger.kt
@@ -24,13 +24,13 @@
     internal val buffer: LogBuffer,
     internal val tag: String,
 ) {
-    /** Logs that we added the chip to a new window. */
-    fun logChipAddition() {
-        buffer.log(tag, LogLevel.DEBUG, {}, { "Chip added" })
+    /** Logs that we added the view in a window titled [windowTitle]. */
+    fun logViewAddition(windowTitle: String) {
+        buffer.log(tag, LogLevel.DEBUG, { str1 = windowTitle }, { "View added. window=$str1" })
     }
 
     /** Logs that we removed the chip for the given [reason]. */
-    fun logChipRemoval(reason: String) {
-        buffer.log(tag, LogLevel.DEBUG, { str1 = reason }, { "Chip removed due to $str1" })
+    fun logViewRemoval(reason: String) {
+        buffer.log(tag, LogLevel.DEBUG, { str1 = reason }, { "View removed due to: $str1" })
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
index b8930a4..87b6e8d 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinator.kt
@@ -38,9 +38,6 @@
 import com.android.systemui.common.ui.binder.TextViewBinder
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.media.taptotransfer.common.MediaTttLogger
-import com.android.systemui.media.taptotransfer.common.MediaTttUtils
-import com.android.systemui.media.taptotransfer.sender.MediaTttSenderLogger
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.policy.ConfigurationController
@@ -64,14 +61,11 @@
  * Only one chipbar may be shown at a time.
  * TODO(b/245610654): Should we just display whichever chipbar was most recently requested, or do we
  *   need to maintain a priority ordering?
- *
- * TODO(b/245610654): Remove all media-related items from this class so it's just for generic
- *   chipbars.
  */
 @SysUISingleton
 open class ChipbarCoordinator @Inject constructor(
         context: Context,
-        @MediaTttSenderLogger logger: MediaTttLogger,
+        logger: ChipbarLogger,
         windowManager: WindowManager,
         @Main mainExecutor: DelayableExecutor,
         accessibilityManager: AccessibilityManager,
@@ -81,7 +75,7 @@
         private val falsingCollector: FalsingCollector,
         private val viewUtil: ViewUtil,
         private val vibratorHelper: VibratorHelper,
-) : TemporaryViewDisplayController<ChipbarInfo, MediaTttLogger>(
+) : TemporaryViewDisplayController<ChipbarInfo, ChipbarLogger>(
         context,
         logger,
         windowManager,
@@ -90,8 +84,6 @@
         configurationController,
         powerManager,
         R.layout.chipbar,
-        MediaTttUtils.WINDOW_TITLE,
-        MediaTttUtils.WAKE_REASON,
 ) {
 
     private lateinit var parent: ChipbarRootView
@@ -106,7 +98,16 @@
         newInfo: ChipbarInfo,
         currentView: ViewGroup
     ) {
-        // TODO(b/245610654): Adding logging here.
+        logger.logViewUpdate(
+            newInfo.windowTitle,
+            newInfo.text.loadText(context),
+            when (newInfo.endItem) {
+                null -> "null"
+                is ChipbarEndItem.Loading -> "loading"
+                is ChipbarEndItem.Error -> "error"
+                is ChipbarEndItem.Button -> "button(${newInfo.endItem.text.loadText(context)})"
+            }
+        )
 
         // Detect falsing touches on the chip.
         parent = currentView.requireViewById(R.id.chipbar_root_view)
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
index 57fde87..6237365 100644
--- a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarInfo.kt
@@ -37,7 +37,10 @@
     val text: Text,
     val endItem: ChipbarEndItem?,
     val vibrationEffect: VibrationEffect? = null,
-) : TemporaryViewInfo
+    override val windowTitle: String,
+    override val wakeReason: String,
+    override val timeoutMs: Int,
+) : TemporaryViewInfo()
 
 /** The possible items to display at the end of the chipbar. */
 sealed class ChipbarEndItem {
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt
new file mode 100644
index 0000000..e477cd6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/chipbar/ChipbarLogger.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2022 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.temporarydisplay.chipbar
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.plugins.log.LogBuffer
+import com.android.systemui.plugins.log.LogLevel
+import com.android.systemui.temporarydisplay.TemporaryViewLogger
+import com.android.systemui.temporarydisplay.dagger.ChipbarLog
+import javax.inject.Inject
+
+/** A logger for the chipbar. */
+@SysUISingleton
+class ChipbarLogger
+@Inject
+constructor(
+    @ChipbarLog buffer: LogBuffer,
+) : TemporaryViewLogger(buffer, "ChipbarLog") {
+    /**
+     * Logs that the chipbar was updated to display in a window named [windowTitle], with [text] and
+     * [endItemDesc].
+     */
+    fun logViewUpdate(windowTitle: String, text: String?, endItemDesc: String) {
+        buffer.log(
+            tag,
+            LogLevel.DEBUG,
+            {
+                str1 = windowTitle
+                str2 = text
+                str3 = endItemDesc
+            },
+            { "Chipbar updated. window=$str1 text=$str2 endItem=$str3" }
+        )
+    }
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl b/packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/ChipbarLog.kt
similarity index 64%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
copy to packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/ChipbarLog.kt
index 1550ab3..5f101f2 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/ChipbarLog.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,6 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.systemui.shared.system;
+package com.android.systemui.temporarydisplay.dagger
 
-parcelable RemoteTransitionCompat;
+import javax.inject.Qualifier
+
+/** Status bar connectivity logs in table format. */
+@Qualifier
+@MustBeDocumented
+@kotlin.annotation.Retention(AnnotationRetention.RUNTIME)
+annotation class ChipbarLog
diff --git a/packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/TemporaryDisplayModule.kt b/packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/TemporaryDisplayModule.kt
new file mode 100644
index 0000000..cf0a183
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/temporarydisplay/dagger/TemporaryDisplayModule.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2022 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.temporarydisplay.dagger
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBufferFactory
+import com.android.systemui.plugins.log.LogBuffer
+import dagger.Module
+import dagger.Provides
+
+@Module
+interface TemporaryDisplayModule {
+    @Module
+    companion object {
+        @JvmStatic
+        @Provides
+        @SysUISingleton
+        @ChipbarLog
+        fun provideChipbarLogBuffer(factory: LogBufferFactory): LogBuffer {
+            return factory.create("ChipbarLog", 40)
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 3ecb15b..5894fd3 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -115,7 +115,6 @@
     private final SecureSettings mSecureSettings;
     private final Executor mMainExecutor;
     private final Handler mBgHandler;
-    private final boolean mIsMonochromaticEnabled;
     private final Context mContext;
     private final boolean mIsMonetEnabled;
     private final UserTracker mUserTracker;
@@ -365,7 +364,6 @@
             UserTracker userTracker, DumpManager dumpManager, FeatureFlags featureFlags,
             @Main Resources resources, WakefulnessLifecycle wakefulnessLifecycle) {
         mContext = context;
-        mIsMonochromaticEnabled = featureFlags.isEnabled(Flags.MONOCHROMATIC_THEMES);
         mIsMonetEnabled = featureFlags.isEnabled(Flags.MONET);
         mDeviceProvisionedController = deviceProvisionedController;
         mBroadcastDispatcher = broadcastDispatcher;
@@ -669,11 +667,8 @@
         // used as a system-wide theme.
         // - Content intentionally excluded, intended for media player, not system-wide
         List<Style> validStyles = new ArrayList<>(Arrays.asList(Style.EXPRESSIVE, Style.SPRITZ,
-                Style.TONAL_SPOT, Style.FRUIT_SALAD, Style.RAINBOW, Style.VIBRANT));
-
-        if (mIsMonochromaticEnabled) {
-            validStyles.add(Style.MONOCHROMATIC);
-        }
+                Style.TONAL_SPOT, Style.FRUIT_SALAD, Style.RAINBOW, Style.VIBRANT,
+                Style.MONOCHROMATIC));
 
         Style style = mThemeStyle;
         final String overlayPackageJson = mSecureSettings.getStringForUser(
diff --git a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
index 10a09dd1..61eadeb 100644
--- a/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/tv/TvSystemUIModule.java
@@ -44,6 +44,7 @@
 import com.android.systemui.recents.Recents;
 import com.android.systemui.recents.RecentsImplementation;
 import com.android.systemui.screenshot.ReferenceScreenshotModule;
+import com.android.systemui.settings.dagger.MultiUserUtilsModule;
 import com.android.systemui.shade.NotificationShadeWindowControllerImpl;
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.shade.ShadeControllerImpl;
@@ -89,6 +90,7 @@
         includes = {
                 AospPolicyModule.class,
                 GestureModule.class,
+                MultiUserUtilsModule.class,
                 PowerModule.class,
                 QSModule.class,
                 ReferenceScreenshotModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
index b16dc54..6a23260 100644
--- a/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/data/repository/UserRepository.kt
@@ -62,6 +62,7 @@
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.withContext
 
 /**
@@ -136,7 +137,7 @@
     private val isNewImpl: Boolean
         get() = !featureFlags.isEnabled(Flags.USER_INTERACTOR_AND_REPO_USE_CONTROLLER)
 
-    private val _userSwitcherSettings = MutableStateFlow<UserSwitcherSettingsModel?>(null)
+    private val _userSwitcherSettings = MutableStateFlow(runBlocking { getSettings() })
     override val userSwitcherSettings: Flow<UserSwitcherSettingsModel> =
         _userSwitcherSettings.asStateFlow().filterNotNull()
 
@@ -235,7 +236,7 @@
     }
 
     override fun isSimpleUserSwitcher(): Boolean {
-        return checkNotNull(_userSwitcherSettings.value?.isSimpleUserSwitcher)
+        return _userSwitcherSettings.value.isSimpleUserSwitcher
     }
 
     private fun observeSelectedUser() {
diff --git a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt
index 07e5cf9..a374885 100644
--- a/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/domain/interactor/GuestUserInteractor.kt
@@ -28,6 +28,8 @@
 import android.view.WindowManagerGlobal
 import android.widget.Toast
 import com.android.internal.logging.UiEventLogger
+import com.android.systemui.GuestResetOrExitSessionReceiver
+import com.android.systemui.GuestResumeSessionReceiver
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
@@ -58,6 +60,8 @@
     private val devicePolicyManager: DevicePolicyManager,
     private val refreshUsersScheduler: RefreshUsersScheduler,
     private val uiEventLogger: UiEventLogger,
+    resumeSessionReceiver: GuestResumeSessionReceiver,
+    resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver,
 ) {
     /** Whether the device is configured to always have a guest user available. */
     val isGuestUserAutoCreated: Boolean = repository.isGuestUserAutoCreated
@@ -65,6 +69,11 @@
     /** Whether the guest user is currently being reset. */
     val isGuestUserResetting: Boolean = repository.isGuestUserResetting
 
+    init {
+        resumeSessionReceiver.register()
+        resetOrExitSessionReceiver.register()
+    }
+
     /** Notifies that the device has finished booting. */
     fun onDeviceBootCompleted() {
         applicationScope.launch {
@@ -208,7 +217,12 @@
             if (newGuestId == UserHandle.USER_NULL) {
                 Log.e(TAG, "Could not create new guest, switching back to system user")
                 switchUser(UserHandle.USER_SYSTEM)
-                withContext(backgroundDispatcher) { manager.removeUser(currentUser.id) }
+                withContext(backgroundDispatcher) {
+                    manager.removeUserWhenPossible(
+                        UserHandle.of(currentUser.id),
+                        /* overrideDevicePolicy= */ false
+                    )
+                }
                 try {
                     WindowManagerGlobal.getWindowManagerService().lockNow(/* options= */ null)
                 } catch (e: RemoteException) {
@@ -222,13 +236,21 @@
 
             switchUser(newGuestId)
 
-            withContext(backgroundDispatcher) { manager.removeUser(currentUser.id) }
+            withContext(backgroundDispatcher) {
+                manager.removeUserWhenPossible(
+                    UserHandle.of(currentUser.id),
+                    /* overrideDevicePolicy= */ false
+                )
+            }
         } else {
             if (repository.isGuestUserAutoCreated) {
                 repository.isGuestUserResetting = true
             }
             switchUser(targetUserId)
-            manager.removeUser(currentUser.id)
+            manager.removeUserWhenPossible(
+                UserHandle.of(currentUser.id),
+                /* overrideDevicePolicy= */ false
+            )
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt b/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt
index 968af59..ad09ee3 100644
--- a/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/user/ui/binder/UserSwitcherViewBinder.kt
@@ -61,14 +61,15 @@
         falsingCollector: FalsingCollector,
         onFinish: () -> Unit,
     ) {
-        val rootView: UserSwitcherRootView = view.requireViewById(R.id.user_switcher_root)
-        val flowWidget: FlowWidget = view.requireViewById(R.id.flow)
+        val gridContainerView: UserSwitcherRootView =
+            view.requireViewById(R.id.user_switcher_grid_container)
+        val flowWidget: FlowWidget = gridContainerView.requireViewById(R.id.flow)
         val addButton: View = view.requireViewById(R.id.add)
         val cancelButton: View = view.requireViewById(R.id.cancel)
         val popupMenuAdapter = MenuAdapter(layoutInflater)
         var popupMenu: UserSwitcherPopupMenu? = null
 
-        rootView.touchHandler =
+        gridContainerView.touchHandler =
             object : Gefingerpoken {
                 override fun onTouchEvent(ev: MotionEvent?): Boolean {
                     falsingCollector.onTouchEvent(ev)
@@ -134,7 +135,7 @@
                         val viewPool =
                             view.children.filter { it.tag == USER_VIEW_TAG }.toMutableList()
                         viewPool.forEach {
-                            view.removeView(it)
+                            gridContainerView.removeView(it)
                             flowWidget.removeView(it)
                         }
                         users.forEach { userViewModel ->
@@ -152,7 +153,7 @@
                                     inflatedView
                                 }
                             userView.id = View.generateViewId()
-                            view.addView(userView)
+                            gridContainerView.addView(userView)
                             flowWidget.addView(userView)
                             UserViewBinder.bind(
                                 view = userView,
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
index 44f6d03..ad97ef4 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
@@ -31,6 +31,7 @@
 import android.os.HandlerThread;
 import android.os.SystemClock;
 import android.os.Trace;
+import android.os.UserHandle;
 import android.service.wallpaper.WallpaperService;
 import android.util.ArraySet;
 import android.util.Log;
@@ -598,7 +599,6 @@
             getDisplayContext().getSystemService(DisplayManager.class)
                     .unregisterDisplayListener(this);
             mWallpaperLocalColorExtractor.cleanUp();
-            unloadBitmap();
         }
 
         @Override
@@ -676,9 +676,14 @@
         void drawFrameOnCanvas(Bitmap bitmap) {
             Trace.beginSection("ImageWallpaper.CanvasEngine#drawFrame");
             Surface surface = mSurfaceHolder.getSurface();
-            Canvas canvas = mWideColorGamut
-                    ? surface.lockHardwareWideColorGamutCanvas()
-                    : surface.lockHardwareCanvas();
+            Canvas canvas = null;
+            try {
+                canvas = mWideColorGamut
+                        ? surface.lockHardwareWideColorGamutCanvas()
+                        : surface.lockHardwareCanvas();
+            } catch (IllegalStateException e) {
+                Log.w(TAG, "Unable to lock canvas", e);
+            }
             if (canvas != null) {
                 Rect dest = mSurfaceHolder.getSurfaceFrame();
                 try {
@@ -709,17 +714,6 @@
             }
         }
 
-        private void unloadBitmap() {
-            mBackgroundExecutor.execute(this::unloadBitmapSynchronized);
-        }
-
-        private void unloadBitmapSynchronized() {
-            synchronized (mLock) {
-                mBitmapUsages = 0;
-                unloadBitmapInternal();
-            }
-        }
-
         private void unloadBitmapInternal() {
             Trace.beginSection("ImageWallpaper.CanvasEngine#unloadBitmap");
             if (mBitmap != null) {
@@ -738,7 +732,7 @@
             boolean loadSuccess = false;
             Bitmap bitmap;
             try {
-                bitmap = mWallpaperManager.getBitmap(false);
+                bitmap = mWallpaperManager.getBitmapAsUser(UserHandle.USER_CURRENT, false);
                 if (bitmap != null
                         && bitmap.getByteCount() > RecordingCanvas.MAX_BITMAP_SIZE) {
                     throw new RuntimeException("Wallpaper is too large to draw!");
@@ -757,7 +751,7 @@
                 }
 
                 try {
-                    bitmap = mWallpaperManager.getBitmap(false);
+                    bitmap = mWallpaperManager.getBitmapAsUser(UserHandle.USER_CURRENT, false);
                 } catch (RuntimeException | OutOfMemoryError e) {
                     Log.w(TAG, "Unable to load default wallpaper!", e);
                     bitmap = null;
@@ -770,9 +764,6 @@
                 Log.e(TAG, "Attempt to load a recycled bitmap");
             } else if (mBitmap == bitmap) {
                 Log.e(TAG, "Loaded a bitmap that was already loaded");
-            } else if (bitmap.getWidth() < 1 || bitmap.getHeight() < 1) {
-                Log.e(TAG, "Attempt to load an invalid wallpaper of length "
-                        + bitmap.getWidth() + "x" + bitmap.getHeight());
             } else {
                 // at this point, loading is done correctly.
                 loadSuccess = true;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index 1c3656d..52b6b38 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -65,7 +65,6 @@
 class ClockEventControllerTest : SysuiTestCase() {
 
     @JvmField @Rule val mockito = MockitoJUnit.rule()
-    @Mock private lateinit var keyguardInteractor: KeyguardInteractor
     @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
     @Mock private lateinit var batteryController: BatteryController
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
index aca60c0..131cf7d 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardListenQueueTest.kt
@@ -72,6 +72,7 @@
     keyguardOccluded = false,
     occludingAppRequestingFp = false,
     primaryUser = false,
+    shouldListenSfpsState = false,
     shouldListenForFingerprintAssistant = false,
     switchingUser = false,
     udfps = false,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
index b885d54..0a2b3d8 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.java
@@ -55,6 +55,7 @@
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.biometrics.SidefpsController;
+import com.android.systemui.classifier.FalsingA11yDelegate;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.log.SessionTracker;
@@ -143,6 +144,8 @@
     private SidefpsController mSidefpsController;
     @Mock
     private KeyguardPasswordViewController mKeyguardPasswordViewControllerMock;
+    @Mock
+    private FalsingA11yDelegate mFalsingA11yDelegate;
 
     @Captor
     private ArgumentCaptor<KeyguardUpdateMonitorCallback> mKeyguardUpdateMonitorCallback;
@@ -186,7 +189,17 @@
                 mKeyguardStateController, mKeyguardSecurityViewFlipperController,
                 mConfigurationController, mFalsingCollector, mFalsingManager,
                 mUserSwitcherController, mFeatureFlags, mGlobalSettings,
-                mSessionTracker, Optional.of(mSidefpsController)).create(mSecurityCallback);
+                mSessionTracker, Optional.of(mSidefpsController), mFalsingA11yDelegate).create(
+                mSecurityCallback);
+    }
+
+    @Test
+    public void onInitConfiguresViewMode() {
+        mKeyguardSecurityContainerController.onInit();
+        verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager),
+                eq(mUserSwitcherController),
+                any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class),
+                eq(mFalsingA11yDelegate));
     }
 
     @Test
@@ -225,7 +238,8 @@
         mKeyguardSecurityContainerController.updateResources();
         verify(mView, never()).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager),
                 eq(mUserSwitcherController),
-                any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class));
+                any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class),
+                eq(mFalsingA11yDelegate));
 
         // Update rotation. Should trigger update
         mConfiguration.orientation = Configuration.ORIENTATION_LANDSCAPE;
@@ -233,7 +247,8 @@
         mKeyguardSecurityContainerController.updateResources();
         verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager),
                 eq(mUserSwitcherController),
-                any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class));
+                any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class),
+                eq(mFalsingA11yDelegate));
     }
 
     private void touchDown() {
@@ -269,7 +284,8 @@
         mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Pattern);
         verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager),
                 eq(mUserSwitcherController),
-                any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class));
+                any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class),
+                eq(mFalsingA11yDelegate));
     }
 
     @Test
@@ -282,7 +298,8 @@
         mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Pattern);
         verify(mView).initMode(eq(MODE_ONE_HANDED), eq(mGlobalSettings), eq(mFalsingManager),
                 eq(mUserSwitcherController),
-                any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class));
+                any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class),
+                eq(mFalsingA11yDelegate));
     }
 
     @Test
@@ -293,7 +310,8 @@
         mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Password);
         verify(mView).initMode(eq(MODE_DEFAULT), eq(mGlobalSettings), eq(mFalsingManager),
                 eq(mUserSwitcherController),
-                any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class));
+                any(KeyguardSecurityContainer.UserSwitcherViewMode.UserSwitcherCallback.class),
+                eq(mFalsingA11yDelegate));
     }
 
     @Test
@@ -307,7 +325,8 @@
         mKeyguardSecurityContainerController.showSecurityScreen(SecurityMode.Password);
         verify(mView).initMode(anyInt(), any(GlobalSettings.class), any(FalsingManager.class),
                 any(UserSwitcherController.class),
-                captor.capture());
+                captor.capture(),
+                eq(mFalsingA11yDelegate));
         captor.getValue().showUnlockToContinueMessage();
         verify(mKeyguardPasswordViewControllerMock).showMessage(
                 getContext().getString(R.string.keyguard_unlock_to_continue), null);
@@ -486,7 +505,7 @@
 
         registeredSwipeListener.onSwipeUp();
 
-        verify(mKeyguardUpdateMonitor).requestFaceAuth(true,
+        verify(mKeyguardUpdateMonitor).requestFaceAuth(
                 FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER);
     }
 
@@ -499,16 +518,15 @@
         registeredSwipeListener.onSwipeUp();
 
         verify(mKeyguardUpdateMonitor, never())
-                .requestFaceAuth(true,
-                        FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER);
+                .requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER);
     }
 
     @Test
     public void onSwipeUp_whenFaceDetectionIsTriggered_hidesBouncerMessage() {
         KeyguardSecurityContainer.SwipeListener registeredSwipeListener =
                 getRegisteredSwipeListener();
-        when(mKeyguardUpdateMonitor.requestFaceAuth(true,
-                FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER)).thenReturn(true);
+        when(mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER))
+                .thenReturn(true);
         setupGetSecurityView();
 
         registeredSwipeListener.onSwipeUp();
@@ -520,8 +538,8 @@
     public void onSwipeUp_whenFaceDetectionIsNotTriggered_retainsBouncerMessage() {
         KeyguardSecurityContainer.SwipeListener registeredSwipeListener =
                 getRegisteredSwipeListener();
-        when(mKeyguardUpdateMonitor.requestFaceAuth(true,
-                FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER)).thenReturn(false);
+        when(mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.SWIPE_UP_ON_BOUNCER))
+                .thenReturn(false);
         setupGetSecurityView();
 
         registeredSwipeListener.onSwipeUp();
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
index 82d3ca7..1bd14e5 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerTest.java
@@ -31,6 +31,7 @@
 
 import static com.android.keyguard.KeyguardSecurityContainer.MODE_DEFAULT;
 import static com.android.keyguard.KeyguardSecurityContainer.MODE_ONE_HANDED;
+import static com.android.keyguard.KeyguardSecurityContainer.MODE_USER_SWITCHER;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -54,6 +55,7 @@
 
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.classifier.FalsingA11yDelegate;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.statusbar.policy.UserSwitcherController;
 import com.android.systemui.user.data.source.UserRecord;
@@ -87,6 +89,8 @@
     private FalsingManager mFalsingManager;
     @Mock
     private UserSwitcherController mUserSwitcherController;
+    @Mock
+    private FalsingA11yDelegate mFalsingA11yDelegate;
 
     private KeyguardSecurityContainer mKeyguardSecurityContainer;
 
@@ -111,15 +115,14 @@
         when(mUserSwitcherController.getCurrentUserName()).thenReturn("Test User");
         when(mUserSwitcherController.isKeyguardShowing()).thenReturn(true);
     }
+
     @Test
     public void testOnApplyWindowInsets() {
         int paddingBottom = getContext().getResources()
                 .getDimensionPixelSize(R.dimen.keyguard_security_view_bottom_margin);
         int imeInsetAmount = paddingBottom + 1;
         int systemBarInsetAmount = 0;
-
-        mKeyguardSecurityContainer.initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager,
-                mUserSwitcherController, () -> {});
+        initMode(MODE_DEFAULT);
 
         Insets imeInset = Insets.of(0, 0, 0, imeInsetAmount);
         Insets systemBarInset = Insets.of(0, 0, 0, systemBarInsetAmount);
@@ -140,8 +143,7 @@
                 .getDimensionPixelSize(R.dimen.keyguard_security_view_bottom_margin);
         int systemBarInsetAmount = paddingBottom + 1;
 
-        mKeyguardSecurityContainer.initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager,
-                mUserSwitcherController, () -> {});
+        initMode(MODE_DEFAULT);
 
         Insets imeInset = Insets.of(0, 0, 0, imeInsetAmount);
         Insets systemBarInset = Insets.of(0, 0, 0, systemBarInsetAmount);
@@ -157,11 +159,8 @@
 
     @Test
     public void testDefaultViewMode() {
-        mKeyguardSecurityContainer.initMode(MODE_ONE_HANDED, mGlobalSettings, mFalsingManager,
-                mUserSwitcherController, () -> {
-                });
-        mKeyguardSecurityContainer.initMode(MODE_DEFAULT, mGlobalSettings, mFalsingManager,
-                mUserSwitcherController, () -> {});
+        initMode(MODE_ONE_HANDED);
+        initMode(MODE_DEFAULT);
         ConstraintSet.Constraint viewFlipperConstraint =
                 getViewConstraint(mSecurityViewFlipper.getId());
         assertThat(viewFlipperConstraint.layout.topToTop).isEqualTo(PARENT_ID);
@@ -377,8 +376,7 @@
 
     private void setupUserSwitcher() {
         when(mGlobalSettings.getInt(any(), anyInt())).thenReturn(ONE_HANDED_KEYGUARD_SIDE_RIGHT);
-        mKeyguardSecurityContainer.initMode(KeyguardSecurityContainer.MODE_USER_SWITCHER,
-                mGlobalSettings, mFalsingManager, mUserSwitcherController, () -> {});
+        initMode(MODE_USER_SWITCHER);
     }
 
     private ArrayList<UserRecord> buildUserRecords(int count) {
@@ -396,8 +394,7 @@
 
     private void setupForUpdateKeyguardPosition(boolean oneHandedMode) {
         int mode = oneHandedMode ? MODE_ONE_HANDED : MODE_DEFAULT;
-        mKeyguardSecurityContainer.initMode(mode, mGlobalSettings, mFalsingManager,
-                mUserSwitcherController, () -> {});
+        initMode(mode);
     }
 
     /** Get the ConstraintLayout constraint of the view. */
@@ -406,4 +403,10 @@
         constraintSet.clone(mKeyguardSecurityContainer);
         return constraintSet.getConstraint(viewId);
     }
+
+    private void initMode(int mode) {
+        mKeyguardSecurityContainer.initMode(mode, mGlobalSettings, mFalsingManager,
+                mUserSwitcherController, () -> {
+                }, mFalsingA11yDelegate);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 66be6ec..5718835 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -21,6 +21,7 @@
 import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_START;
 import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT;
 import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ERROR_LOCKOUT_PERMANENT;
+import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_POWER_BUTTON;
 import static android.telephony.SubscriptionManager.DATA_ROAMING_DISABLE;
 import static android.telephony.SubscriptionManager.NAME_SOURCE_CARRIER_ID;
 
@@ -39,6 +40,7 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.clearInvocations;
+import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
@@ -54,6 +56,7 @@
 import android.app.trust.TrustManager;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -61,18 +64,21 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.content.pm.UserInfo;
+import android.database.ContentObserver;
 import android.hardware.SensorPrivacyManager;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.BiometricSourceType;
 import android.hardware.biometrics.ComponentInfoInternal;
 import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
+import android.hardware.biometrics.SensorProperties;
 import android.hardware.face.FaceManager;
 import android.hardware.face.FaceSensorProperties;
 import android.hardware.face.FaceSensorPropertiesInternal;
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorProperties;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
+import android.net.Uri;
 import android.nfc.NfcAdapter;
 import android.os.Bundle;
 import android.os.CancellationSignal;
@@ -111,6 +117,7 @@
 import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.telephony.TelephonyListenerManager;
 import com.android.systemui.util.settings.GlobalSettings;
+import com.android.systemui.util.settings.SecureSettings;
 
 import org.junit.After;
 import org.junit.Assert;
@@ -182,6 +189,8 @@
     @Mock
     private BroadcastDispatcher mBroadcastDispatcher;
     @Mock
+    private SecureSettings mSecureSettings;
+    @Mock
     private TelephonyManager mTelephonyManager;
     @Mock
     private SensorPrivacyManager mSensorPrivacyManager;
@@ -215,6 +224,7 @@
     private GlobalSettings mGlobalSettings;
     private FaceWakeUpTriggersConfig mFaceWakeUpTriggersConfig;
 
+
     private final int mCurrentUserId = 100;
     private final UserInfo mCurrentUserInfo = new UserInfo(mCurrentUserId, "Test user", 0);
 
@@ -224,6 +234,9 @@
     @Captor
     private ArgumentCaptor<FaceManager.AuthenticationCallback> mAuthenticationCallbackCaptor;
 
+    @Mock
+    private Uri mURI;
+
     // Direct executor
     private final Executor mBackgroundExecutor = Runnable::run;
     private final Executor mMainExecutor = Runnable::run;
@@ -305,6 +318,15 @@
 
         mTestableLooper = TestableLooper.get(this);
         allowTestableLooperAsMainThread();
+
+        when(mSecureSettings.getUriFor(anyString())).thenReturn(mURI);
+
+        final ContentResolver contentResolver = mContext.getContentResolver();
+        ExtendedMockito.spyOn(contentResolver);
+        doNothing().when(contentResolver)
+                .registerContentObserver(any(Uri.class), anyBoolean(), any(ContentObserver.class),
+                        anyInt());
+
         mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext);
 
         verify(mBiometricManager)
@@ -676,7 +698,7 @@
         bouncerFullyVisibleAndNotGoingToSleep();
         mTestableLooper.processAllMessages();
 
-        boolean didFaceAuthRun = mKeyguardUpdateMonitor.requestFaceAuth(true,
+        boolean didFaceAuthRun = mKeyguardUpdateMonitor.requestFaceAuth(
                 NOTIFICATION_PANEL_CLICKED);
 
         assertThat(didFaceAuthRun).isTrue();
@@ -688,7 +710,7 @@
         biometricsDisabledForCurrentUser();
         mTestableLooper.processAllMessages();
 
-        boolean didFaceAuthRun = mKeyguardUpdateMonitor.requestFaceAuth(true,
+        boolean didFaceAuthRun = mKeyguardUpdateMonitor.requestFaceAuth(
                 NOTIFICATION_PANEL_CLICKED);
 
         assertThat(didFaceAuthRun).isFalse();
@@ -707,8 +729,7 @@
         // Stop scanning when bouncer becomes visible
         setKeyguardBouncerVisibility(true);
         clearInvocations(mFaceManager);
-        mKeyguardUpdateMonitor.requestFaceAuth(true,
-                FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
+        mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
         verify(mFaceManager, never()).authenticate(any(), any(), any(), any(), anyInt(),
                 anyBoolean());
     }
@@ -1137,6 +1158,63 @@
     }
 
     @Test
+    public void testStartsListeningForSfps_whenKeyguardIsVisible_ifRequireScreenOnToAuthEnabled()
+            throws RemoteException {
+        // SFPS supported and enrolled
+        final ArrayList<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
+        props.add(newFingerprintSensorPropertiesInternal(TYPE_POWER_BUTTON));
+        when(mAuthController.getSfpsProps()).thenReturn(props);
+        when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
+
+        // WHEN require screen on to auth is disabled, and keyguard is not awake
+        when(mSecureSettings.getIntForUser(anyString(), anyInt(), anyInt())).thenReturn(0);
+        mKeyguardUpdateMonitor.updateSfpsRequireScreenOnToAuthPref();
+
+        mContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.bool.config_requireScreenOnToAuthEnabled, true);
+
+        // Preconditions for sfps auth to run
+        keyguardNotGoingAway();
+        currentUserIsPrimary();
+        currentUserDoesNotHaveTrust();
+        biometricsNotDisabledThroughDevicePolicyManager();
+        biometricsEnabledForCurrentUser();
+        userNotCurrentlySwitching();
+
+        statusBarShadeIsLocked();
+        mTestableLooper.processAllMessages();
+
+        // THEN we should listen for sfps when screen off, because require screen on is disabled
+        assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isTrue();
+
+        // WHEN require screen on to auth is enabled, and keyguard is not awake
+        when(mSecureSettings.getIntForUser(anyString(), anyInt(), anyInt())).thenReturn(1);
+        mKeyguardUpdateMonitor.updateSfpsRequireScreenOnToAuthPref();
+
+        // THEN we shouldn't listen for sfps when screen off, because require screen on is enabled
+        assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isFalse();
+
+        // Device now awake & keyguard is now interactive
+        deviceNotGoingToSleep();
+        deviceIsInteractive();
+        keyguardIsVisible();
+
+        // THEN we should listen for sfps when screen on, and require screen on is enabled
+        assertThat(mKeyguardUpdateMonitor.shouldListenForFingerprint(false)).isTrue();
+    }
+
+    private FingerprintSensorPropertiesInternal newFingerprintSensorPropertiesInternal(
+            @FingerprintSensorProperties.SensorType int sensorType) {
+        return new FingerprintSensorPropertiesInternal(
+                0 /* sensorId */,
+                SensorProperties.STRENGTH_STRONG,
+                1 /* maxEnrollmentsPerUser */,
+                new ArrayList<ComponentInfoInternal>(),
+                sensorType,
+                true /* resetLockoutRequiresHardwareAuthToken */);
+    }
+
+    @Test
     public void testShouldNotListenForUdfps_whenTrustEnabled() {
         // GIVEN a "we should listen for udfps" state
         mStatusBarStateListener.onStateChanged(StatusBarState.KEYGUARD);
@@ -1695,7 +1773,7 @@
     }
 
     private void triggerSuccessfulFaceAuth() {
-        mKeyguardUpdateMonitor.requestFaceAuth(true, FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
+        mKeyguardUpdateMonitor.requestFaceAuth(FaceAuthApiRequestReason.UDFPS_POINTER_DOWN);
         verify(mFaceManager).authenticate(any(),
                 any(),
                 mAuthenticationCallbackCaptor.capture(),
@@ -1805,7 +1883,7 @@
         protected TestableKeyguardUpdateMonitor(Context context) {
             super(context,
                     TestableLooper.get(KeyguardUpdateMonitorTest.this).getLooper(),
-                    mBroadcastDispatcher, mDumpManager,
+                    mBroadcastDispatcher, mSecureSettings, mDumpManager,
                     mBackgroundExecutor, mMainExecutor,
                     mStatusBarStateController, mLockPatternUtils,
                     mAuthController, mTelephonyListenerManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java
index 8ef65dc..a36105e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DismissAnimationControllerTest.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.accessibility.floatingmenu;
 
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
 import android.testing.AndroidTestingRunner;
@@ -47,7 +48,7 @@
                 stubWindowManager);
         final MenuView stubMenuView = new MenuView(mContext, stubMenuViewModel,
                 stubMenuViewAppearance);
-        mDismissView = new DismissView(mContext);
+        mDismissView = spy(new DismissView(mContext));
         mDismissAnimationController = new DismissAnimationController(mDismissView, stubMenuView);
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index d489656..6ec5a6c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -42,6 +42,8 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.keyguard.domain.interactor.BouncerInteractor
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.shade.ShadeExpansionStateManager
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
@@ -103,6 +105,8 @@
     @Mock private lateinit var udfpsView: UdfpsView
     @Mock private lateinit var udfpsEnrollView: UdfpsEnrollView
     @Mock private lateinit var activityLaunchAnimator: ActivityLaunchAnimator
+    @Mock private lateinit var featureFlags: FeatureFlags
+    @Mock private lateinit var bouncerInteractor: BouncerInteractor
     @Captor private lateinit var layoutParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams>
 
     private val onTouch = { _: View, _: MotionEvent, _: Boolean -> true }
@@ -136,7 +140,8 @@
             keyguardUpdateMonitor, dialogManager, dumpManager, transitionController,
             configurationController, systemClock, keyguardStateController,
             unlockedScreenOffAnimationController, udfpsDisplayMode, REQUEST_ID, reason,
-            controllerCallback, onTouch, activityLaunchAnimator, isDebuggable
+            controllerCallback, onTouch, activityLaunchAnimator, featureFlags,
+            bouncerInteractor, isDebuggable
         )
         block()
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 49c6fd1..aa14a40 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -20,6 +20,8 @@
 import static android.view.MotionEvent.ACTION_MOVE;
 import static android.view.MotionEvent.ACTION_UP;
 
+import static com.android.internal.util.FunctionalUtils.ThrowingConsumer;
+
 import static junit.framework.Assert.assertEquals;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -69,7 +71,9 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.keyguard.ScreenLifecycle;
+import com.android.systemui.keyguard.domain.interactor.BouncerInteractor;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.ShadeExpansionStateManager;
@@ -106,9 +110,6 @@
 @RunWithLooper(setAsMainLooper = true)
 public class UdfpsControllerTest extends SysuiTestCase {
 
-    // Use this for inputs going into SystemUI. Use UdfpsController.mUdfpsSensorId for things
-    // leaving SystemUI.
-    private static final int TEST_UDFPS_SENSOR_ID = 1;
     private static final long TEST_REQUEST_ID = 70;
 
     @Rule
@@ -119,7 +120,6 @@
 
     // Dependencies
     private FakeExecutor mBiometricsExecutor;
-    private Execution mExecution;
     @Mock
     private LayoutInflater mLayoutInflater;
     @Mock
@@ -169,6 +169,8 @@
     private FakeExecutor mFgExecutor;
     @Mock
     private UdfpsDisplayMode mUdfpsDisplayMode;
+    @Mock
+    private FeatureFlags mFeatureFlags;
 
     // Stuff for configuring mocks
     @Mock
@@ -189,19 +191,28 @@
     private ActivityLaunchAnimator mActivityLaunchAnimator;
     @Mock
     private AlternateUdfpsTouchProvider mAlternateTouchProvider;
+    @Mock
+    private BouncerInteractor mBouncerInteractor;
 
     // Capture listeners so that they can be used to send events
-    @Captor private ArgumentCaptor<IUdfpsOverlayController> mOverlayCaptor;
+    @Captor
+    private ArgumentCaptor<IUdfpsOverlayController> mOverlayCaptor;
     private IUdfpsOverlayController mOverlayController;
-    @Captor private ArgumentCaptor<UdfpsView.OnTouchListener> mTouchListenerCaptor;
-    @Captor private ArgumentCaptor<View.OnHoverListener> mHoverListenerCaptor;
-    @Captor private ArgumentCaptor<Runnable> mOnDisplayConfiguredCaptor;
-    @Captor private ArgumentCaptor<ScreenLifecycle.Observer> mScreenObserverCaptor;
+    @Captor
+    private ArgumentCaptor<UdfpsView.OnTouchListener> mTouchListenerCaptor;
+    @Captor
+    private ArgumentCaptor<View.OnHoverListener> mHoverListenerCaptor;
+    @Captor
+    private ArgumentCaptor<Runnable> mOnDisplayConfiguredCaptor;
+    @Captor
+    private ArgumentCaptor<ScreenLifecycle.Observer> mScreenObserverCaptor;
     private ScreenLifecycle.Observer mScreenObserver;
+    private FingerprintSensorPropertiesInternal mOpticalProps;
+    private FingerprintSensorPropertiesInternal mUltrasonicProps;
 
     @Before
     public void setUp() {
-        mExecution = new FakeExecution();
+        Execution execution = new FakeExecution();
 
         when(mLayoutInflater.inflate(R.layout.udfps_view, null, false))
                 .thenReturn(mUdfpsView);
@@ -214,9 +225,7 @@
         when(mLayoutInflater.inflate(R.layout.udfps_fpm_other_view, null))
                 .thenReturn(mFpmOtherView);
         when(mEnrollView.getContext()).thenReturn(mContext);
-        when(mKeyguardStateController.isOccluded()).thenReturn(false);
         when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
-        final List<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
 
         final List<ComponentInfoInternal> componentInfo = new ArrayList<>();
         componentInfo.add(new ComponentInfoInternal("faceSensor" /* componentId */,
@@ -226,13 +235,25 @@
                 "" /* hardwareVersion */, "" /* firmwareVersion */, "" /* serialNumber */,
                 "vendor/version/revision" /* softwareVersion */));
 
-        props.add(new FingerprintSensorPropertiesInternal(TEST_UDFPS_SENSOR_ID,
+        mOpticalProps = new FingerprintSensorPropertiesInternal(1 /* sensorId */,
                 SensorProperties.STRENGTH_STRONG,
                 5 /* maxEnrollmentsPerUser */,
                 componentInfo,
                 FingerprintSensorProperties.TYPE_UDFPS_OPTICAL,
-                true /* resetLockoutRequiresHardwareAuthToken */));
+                true /* resetLockoutRequiresHardwareAuthToken */);
+
+        mUltrasonicProps = new FingerprintSensorPropertiesInternal(2 /* sensorId */,
+                SensorProperties.STRENGTH_STRONG,
+                5 /* maxEnrollmentsPerUser */,
+                componentInfo,
+                FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC,
+                true /* resetLockoutRequiresHardwareAuthToken */);
+
+        List<FingerprintSensorPropertiesInternal> props = new ArrayList<>();
+        props.add(mOpticalProps);
+        props.add(mUltrasonicProps);
         when(mFingerprintManager.getSensorPropertiesInternal()).thenReturn(props);
+
         mFgExecutor = new FakeExecutor(new FakeSystemClock());
 
         // Create a fake background executor.
@@ -240,7 +261,7 @@
 
         mUdfpsController = new UdfpsController(
                 mContext,
-                mExecution,
+                execution,
                 mLayoutInflater,
                 mFingerprintManager,
                 mWindowManager,
@@ -250,6 +271,7 @@
                 mStatusBarKeyguardViewManager,
                 mDumpManager,
                 mKeyguardUpdateMonitor,
+                mFeatureFlags,
                 mFalsingManager,
                 mPowerManager,
                 mAccessibilityManager,
@@ -268,18 +290,19 @@
                 mLatencyTracker,
                 mActivityLaunchAnimator,
                 Optional.of(mAlternateTouchProvider),
-                mBiometricsExecutor);
+                mBiometricsExecutor,
+                mBouncerInteractor);
         verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
         mOverlayController = mOverlayCaptor.getValue();
         verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture());
         mScreenObserver = mScreenObserverCaptor.getValue();
-        mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID, new UdfpsOverlayParams());
+        mUdfpsController.updateOverlayParams(mOpticalProps, new UdfpsOverlayParams());
         mUdfpsController.setUdfpsDisplayMode(mUdfpsDisplayMode);
     }
 
     @Test
     public void dozeTimeTick() throws RemoteException {
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
                 BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mFgExecutor.runAllReady();
         mUdfpsController.dozeTimeTick();
@@ -294,7 +317,7 @@
         when(mUdfpsView.getAnimationViewController()).thenReturn(mUdfpsKeyguardViewController);
 
         // GIVEN that the overlay is showing
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
                 BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mFgExecutor.runAllReady();
 
@@ -329,7 +352,7 @@
         when(mUdfpsView.getAnimationViewController()).thenReturn(mUdfpsKeyguardViewController);
 
         // GIVEN that the overlay is showing
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
                 BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mFgExecutor.runAllReady();
 
@@ -337,7 +360,7 @@
         verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
         MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0);
         if (stale) {
-            mOverlayController.hideUdfpsOverlay(TEST_UDFPS_SENSOR_ID);
+            mOverlayController.hideUdfpsOverlay(mOpticalProps.sensorId);
             mFgExecutor.runAllReady();
         }
         mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent);
@@ -357,7 +380,7 @@
         when(mUdfpsView.getAnimationViewController()).thenReturn(mUdfpsKeyguardViewController);
 
         // GIVEN that the overlay is showing
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
                 BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mFgExecutor.runAllReady();
 
@@ -380,12 +403,12 @@
     @Test
     public void hideUdfpsOverlay_resetsAltAuthBouncerWhenShowing() throws RemoteException {
         // GIVEN overlay was showing and the udfps bouncer is showing
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
                 BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         when(mStatusBarKeyguardViewManager.isShowingAlternateAuth()).thenReturn(true);
 
         // WHEN the overlay is hidden
-        mOverlayController.hideUdfpsOverlay(TEST_UDFPS_SENSOR_ID);
+        mOverlayController.hideUdfpsOverlay(mOpticalProps.sensorId);
         mFgExecutor.runAllReady();
 
         // THEN the udfps bouncer is reset
@@ -394,13 +417,13 @@
 
     @Test
     public void testSubscribesToOrientationChangesWhenShowingOverlay() throws Exception {
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
                 BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mFgExecutor.runAllReady();
 
         verify(mDisplayManager).registerDisplayListener(any(), eq(mHandler), anyLong());
 
-        mOverlayController.hideUdfpsOverlay(TEST_UDFPS_SENSOR_ID);
+        mOverlayController.hideUdfpsOverlay(mOpticalProps.sensorId);
         mFgExecutor.runAllReady();
 
         verify(mDisplayManager).unregisterDisplayListener(any());
@@ -430,12 +453,12 @@
                             }
 
                             // Initialize the overlay with old parameters.
-                            mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID, oldParams);
+                            mUdfpsController.updateOverlayParams(mOpticalProps, oldParams);
 
                             // Show the overlay.
                             reset(mWindowManager);
                             mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID,
-                                    TEST_UDFPS_SENSOR_ID,
+                                    mOpticalProps.sensorId,
                                     BiometricOverlayConstants.REASON_ENROLL_ENROLLING,
                                     mUdfpsOverlayControllerCallback);
                             mFgExecutor.runAllReady();
@@ -443,7 +466,7 @@
 
                             // Update overlay parameters.
                             reset(mWindowManager);
-                            mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID, newParams);
+                            mUdfpsController.updateOverlayParams(mOpticalProps, newParams);
                             mFgExecutor.runAllReady();
 
                             // Ensure the overlay was recreated.
@@ -465,18 +488,18 @@
         final int rotation = Surface.ROTATION_0;
 
         // Initialize the overlay.
-        mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID,
+        mUdfpsController.updateOverlayParams(mOpticalProps,
                 new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
                         scaleFactor, rotation));
 
         // Show the overlay.
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
                 BiometricOverlayConstants.REASON_ENROLL_ENROLLING, mUdfpsOverlayControllerCallback);
         mFgExecutor.runAllReady();
         verify(mWindowManager).addView(any(), any());
 
         // Update overlay with the same parameters.
-        mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID,
+        mUdfpsController.updateOverlayParams(mOpticalProps,
                 new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
                         scaleFactor, rotation));
         mFgExecutor.runAllReady();
@@ -518,13 +541,13 @@
         when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
 
         // Show the overlay.
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
                 BiometricOverlayConstants.REASON_ENROLL_ENROLLING, mUdfpsOverlayControllerCallback);
         mFgExecutor.runAllReady();
         verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
 
         // Test ROTATION_0
-        mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID,
+        mUdfpsController.updateOverlayParams(mOpticalProps,
                 new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
                         scaleFactor, Surface.ROTATION_0));
         MotionEvent event = obtainMotionEvent(ACTION_DOWN, displayWidth, displayHeight, touchMinor,
@@ -541,7 +564,7 @@
 
         // Test ROTATION_90
         reset(mAlternateTouchProvider);
-        mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID,
+        mUdfpsController.updateOverlayParams(mOpticalProps,
                 new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
                         scaleFactor, Surface.ROTATION_90));
         event = obtainMotionEvent(ACTION_DOWN, displayHeight, 0, touchMinor, touchMajor);
@@ -557,7 +580,7 @@
 
         // Test ROTATION_270
         reset(mAlternateTouchProvider);
-        mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID,
+        mUdfpsController.updateOverlayParams(mOpticalProps,
                 new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
                         scaleFactor, Surface.ROTATION_270));
         event = obtainMotionEvent(ACTION_DOWN, 0, displayWidth, touchMinor, touchMajor);
@@ -573,7 +596,7 @@
 
         // Test ROTATION_180
         reset(mAlternateTouchProvider);
-        mUdfpsController.updateOverlayParams(TEST_UDFPS_SENSOR_ID,
+        mUdfpsController.updateOverlayParams(mOpticalProps,
                 new UdfpsOverlayParams(sensorBounds, sensorBounds, displayWidth, displayHeight,
                         scaleFactor, Surface.ROTATION_180));
         // ROTATION_180 is not supported. It should be treated like ROTATION_0.
@@ -589,63 +612,108 @@
                 eq(expectedY), eq(expectedMinor), eq(expectedMajor));
     }
 
+    private void runForAllUdfpsTypes(
+            ThrowingConsumer<FingerprintSensorPropertiesInternal> sensorPropsConsumer) {
+        for (FingerprintSensorPropertiesInternal sensorProps : List.of(mOpticalProps,
+                mUltrasonicProps)) {
+            mUdfpsController.updateOverlayParams(sensorProps, new UdfpsOverlayParams());
+            sensorPropsConsumer.accept(sensorProps);
+        }
+    }
+
     @Test
-    public void fingerDown() throws RemoteException {
+    public void fingerDown() {
+        runForAllUdfpsTypes(this::fingerDownForSensor);
+    }
+
+    private void fingerDownForSensor(FingerprintSensorPropertiesInternal sensorProps)
+            throws RemoteException {
+        reset(mUdfpsView, mAlternateTouchProvider, mFingerprintManager, mLatencyTracker,
+                mKeyguardUpdateMonitor);
+
         // Configure UdfpsView to accept the ACTION_DOWN event
         when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
         when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
         when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
 
         // GIVEN that the overlay is showing
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId,
                 BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mFgExecutor.runAllReady();
-        // WHEN ACTION_DOWN is received
         verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
+
+        // WHEN ACTION_DOWN is received
         MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
         mTouchListenerCaptor.getValue().onTouch(mUdfpsView, downEvent);
         mBiometricsExecutor.runAllReady();
         downEvent.recycle();
-        MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0);
 
-        // FIX THIS TEST
+        MotionEvent moveEvent = MotionEvent.obtain(0, 0, MotionEvent.ACTION_MOVE, 0, 0, 0);
         mTouchListenerCaptor.getValue().onTouch(mUdfpsView, moveEvent);
         mBiometricsExecutor.runAllReady();
         moveEvent.recycle();
+
         mFgExecutor.runAllReady();
+
         // THEN FingerprintManager is notified about onPointerDown
         verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID), eq(0), eq(0), eq(0f),
                 eq(0f));
         verify(mFingerprintManager, never()).onPointerDown(anyLong(), anyInt(), anyInt(), anyInt(),
                 anyFloat(), anyFloat());
-        verify(mLatencyTracker).onActionStart(eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
+
         // AND display configuration begins
-        verify(mUdfpsView).configureDisplay(mOnDisplayConfiguredCaptor.capture());
+        if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+            verify(mLatencyTracker).onActionStart(eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
+            verify(mUdfpsView).configureDisplay(mOnDisplayConfiguredCaptor.capture());
+        } else {
+            verify(mLatencyTracker, never()).onActionStart(
+                    eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
+            verify(mUdfpsView, never()).configureDisplay(any());
+        }
         verify(mLatencyTracker, never()).onActionEnd(eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
         verify(mKeyguardUpdateMonitor).onUdfpsPointerDown(eq((int) TEST_REQUEST_ID));
-        // AND onDisplayConfigured notifies FingerprintManager about onUiReady
-        mOnDisplayConfiguredCaptor.getValue().run();
-        mBiometricsExecutor.runAllReady();
-        InOrder inOrder = inOrder(mAlternateTouchProvider, mLatencyTracker);
-        inOrder.verify(mAlternateTouchProvider).onUiReady();
-        inOrder.verify(mLatencyTracker).onActionEnd(eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
+
+        if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+            // AND onDisplayConfigured notifies FingerprintManager about onUiReady
+            mOnDisplayConfiguredCaptor.getValue().run();
+            mBiometricsExecutor.runAllReady();
+            InOrder inOrder = inOrder(mAlternateTouchProvider, mLatencyTracker);
+            inOrder.verify(mAlternateTouchProvider).onUiReady();
+            inOrder.verify(mLatencyTracker).onActionEnd(eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
+        } else {
+            verify(mAlternateTouchProvider, never()).onUiReady();
+            verify(mLatencyTracker, never()).onActionEnd(
+                    eq(LatencyTracker.ACTION_UDFPS_ILLUMINATE));
+        }
     }
 
     @Test
-    public void aodInterrupt() throws RemoteException {
+    public void aodInterrupt() {
+        runForAllUdfpsTypes(this::aodInterruptForSensor);
+    }
+
+    private void aodInterruptForSensor(FingerprintSensorPropertiesInternal sensorProps)
+            throws RemoteException {
+        mUdfpsController.cancelAodInterrupt();
+        reset(mUdfpsView, mAlternateTouchProvider, mFingerprintManager, mKeyguardUpdateMonitor);
+        when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
+
         // GIVEN that the overlay is showing and screen is on and fp is running
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId,
                 BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mScreenObserver.onScreenTurnedOn();
         mFgExecutor.runAllReady();
-        when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
         // WHEN fingerprint is requested because of AOD interrupt
         mUdfpsController.onAodInterrupt(0, 0, 2f, 3f);
         mFgExecutor.runAllReady();
-        // THEN display configuration begins
-        // AND onDisplayConfigured notifies FingerprintManager about onUiReady
-        verify(mUdfpsView).configureDisplay(mOnDisplayConfiguredCaptor.capture());
-        mOnDisplayConfiguredCaptor.getValue().run();
+        if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+            // THEN display configuration begins
+            // AND onDisplayConfigured notifies FingerprintManager about onUiReady
+            verify(mUdfpsView).configureDisplay(mOnDisplayConfiguredCaptor.capture());
+            mOnDisplayConfiguredCaptor.getValue().run();
+        } else {
+            verify(mUdfpsView, never()).configureDisplay(mOnDisplayConfiguredCaptor.capture());
+        }
         mBiometricsExecutor.runAllReady();
         verify(mAlternateTouchProvider).onPointerDown(eq(TEST_REQUEST_ID),
                 eq(0), eq(0), eq(3f) /* minor */, eq(2f) /* major */);
@@ -655,54 +723,92 @@
     }
 
     @Test
-    public void cancelAodInterrupt() throws RemoteException {
+    public void cancelAodInterrupt() {
+        runForAllUdfpsTypes(this::cancelAodInterruptForSensor);
+    }
+
+    private void cancelAodInterruptForSensor(FingerprintSensorPropertiesInternal sensorProps)
+            throws RemoteException {
+        reset(mUdfpsView);
+
         // GIVEN AOD interrupt
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId,
                 BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mScreenObserver.onScreenTurnedOn();
         mFgExecutor.runAllReady();
-        when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
         mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
-        when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
-        // WHEN it is cancelled
-        mUdfpsController.cancelAodInterrupt();
-        // THEN the display is unconfigured
-        verify(mUdfpsView).unconfigureDisplay();
+        if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+            when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+            // WHEN it is cancelled
+            mUdfpsController.cancelAodInterrupt();
+            // THEN the display is unconfigured
+            verify(mUdfpsView).unconfigureDisplay();
+        } else {
+            when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
+            // WHEN it is cancelled
+            mUdfpsController.cancelAodInterrupt();
+            // THEN the display configuration is unchanged.
+            verify(mUdfpsView, never()).unconfigureDisplay();
+        }
     }
 
     @Test
-    public void aodInterruptTimeout() throws RemoteException {
+    public void aodInterruptTimeout() {
+        runForAllUdfpsTypes(this::aodInterruptTimeoutForSensor);
+    }
+
+    private void aodInterruptTimeoutForSensor(FingerprintSensorPropertiesInternal sensorProps)
+            throws RemoteException {
+        reset(mUdfpsView);
+
         // GIVEN AOD interrupt
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId,
                 BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mScreenObserver.onScreenTurnedOn();
         mFgExecutor.runAllReady();
-        when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
         mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
         mFgExecutor.runAllReady();
-        when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+        if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+            when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+        } else {
+            when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
+        }
         // WHEN it times out
         mFgExecutor.advanceClockToNext();
         mFgExecutor.runAllReady();
-        // THEN the display is unconfigured
-        verify(mUdfpsView).unconfigureDisplay();
+        if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+            // THEN the display is unconfigured.
+            verify(mUdfpsView).unconfigureDisplay();
+        } else {
+            // THEN the display configuration is unchanged.
+            verify(mUdfpsView, never()).unconfigureDisplay();
+        }
     }
 
     @Test
-    public void aodInterruptCancelTimeoutActionOnFingerUp() throws RemoteException {
+    public void aodInterruptCancelTimeoutActionOnFingerUp() {
+        runForAllUdfpsTypes(this::aodInterruptCancelTimeoutActionOnFingerUpForSensor);
+    }
+
+    private void aodInterruptCancelTimeoutActionOnFingerUpForSensor(
+            FingerprintSensorPropertiesInternal sensorProps) throws RemoteException {
+        reset(mUdfpsView);
         when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
-        when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
 
         // GIVEN AOD interrupt
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId,
                 BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mScreenObserver.onScreenTurnedOn();
         mFgExecutor.runAllReady();
         mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
         mFgExecutor.runAllReady();
 
-        // Configure UdfpsView to accept the ACTION_UP event
-        when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+        if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+            // Configure UdfpsView to accept the ACTION_UP event
+            when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+        } else {
+            when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
+        }
 
         // WHEN ACTION_UP is received
         verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
@@ -727,37 +833,54 @@
         moveEvent.recycle();
         mFgExecutor.runAllReady();
 
-        // Configure UdfpsView to accept the finger up event
-        when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+        if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+            // Configure UdfpsView to accept the finger up event
+            when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+        } else {
+            when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
+        }
 
         // WHEN it times out
         mFgExecutor.advanceClockToNext();
         mFgExecutor.runAllReady();
 
-        // THEN the display should be unconfigured once. If the timeout action is not
-        // cancelled, the display would be unconfigured twice which would cause two
-        // FP attempts.
-        verify(mUdfpsView, times(1)).unconfigureDisplay();
+        if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+            // THEN the display should be unconfigured once. If the timeout action is not
+            // cancelled, the display would be unconfigured twice which would cause two
+            // FP attempts.
+            verify(mUdfpsView, times(1)).unconfigureDisplay();
+        } else {
+            verify(mUdfpsView, never()).unconfigureDisplay();
+        }
     }
 
     @Test
-    public void aodInterruptCancelTimeoutActionOnAcquired() throws RemoteException {
+    public void aodInterruptCancelTimeoutActionOnAcquired() {
+        runForAllUdfpsTypes(this::aodInterruptCancelTimeoutActionOnAcquiredForSensor);
+    }
+
+    private void aodInterruptCancelTimeoutActionOnAcquiredForSensor(
+            FingerprintSensorPropertiesInternal sensorProps) throws RemoteException {
+        reset(mUdfpsView);
         when(mUdfpsView.isWithinSensorArea(anyFloat(), anyFloat())).thenReturn(true);
-        when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
 
         // GIVEN AOD interrupt
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId,
                 BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mScreenObserver.onScreenTurnedOn();
         mFgExecutor.runAllReady();
         mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
         mFgExecutor.runAllReady();
 
-        // Configure UdfpsView to accept the acquired event
-        when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+        if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+            // Configure UdfpsView to accept the acquired event
+            when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+        } else {
+            when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
+        }
 
         // WHEN acquired is received
-        mOverlayController.onAcquired(TEST_UDFPS_SENSOR_ID,
+        mOverlayController.onAcquired(sensorProps.sensorId,
                 BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD);
 
         // Configure UdfpsView to accept the ACTION_DOWN event
@@ -777,29 +900,43 @@
         moveEvent.recycle();
         mFgExecutor.runAllReady();
 
-        // Configure UdfpsView to accept the finger up event
-        when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+        if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+            // Configure UdfpsView to accept the finger up event
+            when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
+        } else {
+            when(mUdfpsView.isDisplayConfigured()).thenReturn(false);
+        }
 
         // WHEN it times out
         mFgExecutor.advanceClockToNext();
         mFgExecutor.runAllReady();
 
-        // THEN the display should be unconfigured once. If the timeout action is not
-        // cancelled, the display would be unconfigured twice which would cause two
-        // FP attempts.
-        verify(mUdfpsView, times(1)).unconfigureDisplay();
+        if (sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
+            // THEN the display should be unconfigured once. If the timeout action is not
+            // cancelled, the display would be unconfigured twice which would cause two
+            // FP attempts.
+            verify(mUdfpsView, times(1)).unconfigureDisplay();
+        } else {
+            verify(mUdfpsView, never()).unconfigureDisplay();
+        }
     }
 
     @Test
-    public void aodInterruptScreenOff() throws RemoteException {
+    public void aodInterruptScreenOff() {
+        runForAllUdfpsTypes(this::aodInterruptScreenOffForSensor);
+    }
+
+    private void aodInterruptScreenOffForSensor(FingerprintSensorPropertiesInternal sensorProps)
+            throws RemoteException {
+        reset(mUdfpsView);
+
         // GIVEN screen off
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId,
                 BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mScreenObserver.onScreenTurnedOff();
         mFgExecutor.runAllReady();
 
         // WHEN aod interrupt is received
-        when(mKeyguardUpdateMonitor.isFingerprintDetectionRunning()).thenReturn(true);
         mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
 
         // THEN display doesn't get configured because it's off
@@ -807,9 +944,16 @@
     }
 
     @Test
-    public void aodInterrupt_fingerprintNotRunning() throws RemoteException {
+    public void aodInterrupt_fingerprintNotRunning() {
+        runForAllUdfpsTypes(this::aodInterrupt_fingerprintNotRunningForSensor);
+    }
+
+    private void aodInterrupt_fingerprintNotRunningForSensor(
+            FingerprintSensorPropertiesInternal sensorProps) throws RemoteException {
+        reset(mUdfpsView);
+
         // GIVEN showing overlay
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, sensorProps.sensorId,
                 BiometricOverlayConstants.REASON_AUTH_KEYGUARD,
                 mUdfpsOverlayControllerCallback);
         mScreenObserver.onScreenTurnedOn();
@@ -831,7 +975,7 @@
 
         // GIVEN that the overlay is showing and a11y touch exploration enabled
         when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(true);
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
                 BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mFgExecutor.runAllReady();
 
@@ -866,7 +1010,7 @@
 
         // GIVEN that the overlay is showing and a11y touch exploration NOT enabled
         when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(false);
-        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, TEST_UDFPS_SENSOR_ID,
+        mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
                 BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mFgExecutor.runAllReady();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDisplayModeTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDisplayModeTest.java
index 7864f21b..1bc237d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDisplayModeTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsDisplayModeTest.java
@@ -23,7 +23,7 @@
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
-import android.hardware.fingerprint.IUdfpsHbmListener;
+import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
 import android.os.RemoteException;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
@@ -48,7 +48,7 @@
     @Mock
     private AuthController mAuthController;
     @Mock
-    private IUdfpsHbmListener mDisplayCallback;
+    private IUdfpsRefreshRateRequestCallback mDisplayCallback;
     @Mock
     private UdfpsLogger mUdfpsLogger;
     @Mock
@@ -68,7 +68,7 @@
         when(contextSpy.getDisplayId()).thenReturn(DISPLAY_ID);
 
         // Set up mocks.
-        when(mAuthController.getUdfpsHbmListener()).thenReturn(mDisplayCallback);
+        when(mAuthController.getUdfpsRefreshRateCallback()).thenReturn(mDisplayCallback);
 
         // Create a real controller with mock dependencies.
         mHbmController = new UdfpsDisplayMode(contextSpy, mExecution, mAuthController,
@@ -81,7 +81,7 @@
         mHbmController.enable(mOnEnabled);
 
         // Should set the appropriate refresh rate for UDFPS and notify the caller.
-        verify(mDisplayCallback).onHbmEnabled(eq(DISPLAY_ID));
+        verify(mDisplayCallback).onRequestEnabled(eq(DISPLAY_ID));
         verify(mOnEnabled).run();
 
         // Disable the UDFPS mode.
@@ -89,7 +89,7 @@
 
         // Should unset the refresh rate and notify the caller.
         verify(mOnDisabled).run();
-        verify(mDisplayCallback).onHbmDisabled(eq(DISPLAY_ID));
+        verify(mDisplayCallback).onRequestDisabled(eq(DISPLAY_ID));
     }
 
     @Test
@@ -98,7 +98,7 @@
         mHbmController.enable(mOnEnabled);
 
         // Should set the appropriate refresh rate for UDFPS and notify the caller.
-        verify(mDisplayCallback).onHbmEnabled(eq(DISPLAY_ID));
+        verify(mDisplayCallback).onRequestEnabled(eq(DISPLAY_ID));
         verify(mOnEnabled).run();
 
         // Second request to enable the UDFPS mode, while it's still enabled.
@@ -115,7 +115,7 @@
         mHbmController.enable(mOnEnabled);
 
         // Should set the appropriate refresh rate for UDFPS and notify the caller.
-        verify(mDisplayCallback).onHbmEnabled(eq(DISPLAY_ID));
+        verify(mDisplayCallback).onRequestEnabled(eq(DISPLAY_ID));
         verify(mOnEnabled).run();
 
         // First request to disable the UDFPS mode.
@@ -123,7 +123,7 @@
 
         // Should unset the refresh rate and notify the caller.
         verify(mOnDisabled).run();
-        verify(mDisplayCallback).onHbmDisabled(eq(DISPLAY_ID));
+        verify(mDisplayCallback).onRequestDisabled(eq(DISPLAY_ID));
 
         // Second request to disable the UDFPS mode, when it's already disabled.
         mHbmController.disable(mOnDisabled);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java
new file mode 100644
index 0000000..e5c7a42
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerBaseTest.java
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics;
+
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+
+import com.android.keyguard.KeyguardUpdateMonitor;
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.KeyguardViewMediator;
+import com.android.systemui.keyguard.domain.interactor.BouncerInteractor;
+import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.shade.ShadeExpansionChangeEvent;
+import com.android.systemui.shade.ShadeExpansionListener;
+import com.android.systemui.shade.ShadeExpansionStateManager;
+import com.android.systemui.statusbar.LockscreenShadeTransitionController;
+import com.android.systemui.statusbar.phone.KeyguardBouncer;
+import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
+import com.android.systemui.statusbar.phone.SystemUIDialogManager;
+import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
+import com.android.systemui.statusbar.policy.ConfigurationController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
+import com.android.systemui.util.concurrency.DelayableExecutor;
+import com.android.systemui.util.time.FakeSystemClock;
+
+import org.junit.Before;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.List;
+
+public class UdfpsKeyguardViewControllerBaseTest extends SysuiTestCase {
+    // Dependencies
+    protected @Mock UdfpsKeyguardView mView;
+    protected @Mock Context mResourceContext;
+    protected @Mock StatusBarStateController mStatusBarStateController;
+    protected @Mock ShadeExpansionStateManager mShadeExpansionStateManager;
+    protected @Mock StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
+    protected @Mock LockscreenShadeTransitionController mLockscreenShadeTransitionController;
+    protected @Mock DumpManager mDumpManager;
+    protected @Mock DelayableExecutor mExecutor;
+    protected @Mock KeyguardUpdateMonitor mKeyguardUpdateMonitor;
+    protected @Mock KeyguardStateController mKeyguardStateController;
+    protected @Mock KeyguardViewMediator mKeyguardViewMediator;
+    protected @Mock ConfigurationController mConfigurationController;
+    protected @Mock UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
+    protected @Mock SystemUIDialogManager mDialogManager;
+    protected @Mock UdfpsController mUdfpsController;
+    protected @Mock ActivityLaunchAnimator mActivityLaunchAnimator;
+    protected @Mock KeyguardBouncer mBouncer;
+    protected @Mock BouncerInteractor mBouncerInteractor;
+
+    protected FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
+    protected FakeSystemClock mSystemClock = new FakeSystemClock();
+
+    protected UdfpsKeyguardViewController mController;
+
+    // Capture listeners so that they can be used to send events
+    private @Captor ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerCaptor;
+    protected StatusBarStateController.StateListener mStatusBarStateListener;
+
+    private @Captor ArgumentCaptor<ShadeExpansionListener> mExpansionListenerCaptor;
+    protected List<ShadeExpansionListener> mExpansionListeners;
+
+    private @Captor ArgumentCaptor<StatusBarKeyguardViewManager.AlternateAuthInterceptor>
+            mAltAuthInterceptorCaptor;
+    protected StatusBarKeyguardViewManager.AlternateAuthInterceptor mAltAuthInterceptor;
+
+    private @Captor ArgumentCaptor<KeyguardStateController.Callback>
+            mKeyguardStateControllerCallbackCaptor;
+    protected KeyguardStateController.Callback mKeyguardStateControllerCallback;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        when(mView.getContext()).thenReturn(mResourceContext);
+        when(mResourceContext.getString(anyInt())).thenReturn("test string");
+        when(mKeyguardViewMediator.isAnimatingScreenOff()).thenReturn(false);
+        when(mView.getUnpausedAlpha()).thenReturn(255);
+        mController = createUdfpsKeyguardViewController();
+    }
+
+    protected void sendStatusBarStateChanged(int statusBarState) {
+        mStatusBarStateListener.onStateChanged(statusBarState);
+    }
+
+    protected void captureStatusBarStateListeners() {
+        verify(mStatusBarStateController).addCallback(mStateListenerCaptor.capture());
+        mStatusBarStateListener = mStateListenerCaptor.getValue();
+    }
+
+    protected void captureStatusBarExpansionListeners() {
+        verify(mShadeExpansionStateManager, times(2))
+                .addExpansionListener(mExpansionListenerCaptor.capture());
+        // first (index=0) is from super class, UdfpsAnimationViewController.
+        // second (index=1) is from UdfpsKeyguardViewController
+        mExpansionListeners = mExpansionListenerCaptor.getAllValues();
+    }
+
+    protected void updateStatusBarExpansion(float fraction, boolean expanded) {
+        ShadeExpansionChangeEvent event =
+                new ShadeExpansionChangeEvent(
+                        fraction, expanded, /* tracking= */ false, /* dragDownPxAmount= */ 0f);
+        for (ShadeExpansionListener listener : mExpansionListeners) {
+            listener.onPanelExpansionChanged(event);
+        }
+    }
+
+    protected void captureAltAuthInterceptor() {
+        verify(mStatusBarKeyguardViewManager).setAlternateAuthInterceptor(
+                mAltAuthInterceptorCaptor.capture());
+        mAltAuthInterceptor = mAltAuthInterceptorCaptor.getValue();
+    }
+
+    protected void captureKeyguardStateControllerCallback() {
+        verify(mKeyguardStateController).addCallback(
+                mKeyguardStateControllerCallbackCaptor.capture());
+        mKeyguardStateControllerCallback = mKeyguardStateControllerCallbackCaptor.getValue();
+    }
+
+    public UdfpsKeyguardViewController createUdfpsKeyguardViewController() {
+        return createUdfpsKeyguardViewController(false);
+    }
+
+    protected UdfpsKeyguardViewController createUdfpsKeyguardViewController(
+            boolean useModernBouncer) {
+        mFeatureFlags.set(Flags.MODERN_BOUNCER, useModernBouncer);
+        when(mStatusBarKeyguardViewManager.getBouncer()).thenReturn(
+                useModernBouncer ? null : mBouncer);
+        return new UdfpsKeyguardViewController(
+                mView,
+                mStatusBarStateController,
+                mShadeExpansionStateManager,
+                mStatusBarKeyguardViewManager,
+                mKeyguardUpdateMonitor,
+                mDumpManager,
+                mLockscreenShadeTransitionController,
+                mConfigurationController,
+                mSystemClock,
+                mKeyguardStateController,
+                mUnlockedScreenOffAnimationController,
+                mDialogManager,
+                mUdfpsController,
+                mActivityLaunchAnimator,
+                mFeatureFlags,
+                mBouncerInteractor);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
index c0f9c82..55b6194 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerTest.java
@@ -25,126 +25,53 @@
 import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
-import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.content.Context;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
 
 import androidx.test.filters.SmallTest;
 
-import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.animation.ActivityLaunchAnimator;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.keyguard.KeyguardViewMediator;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shade.ShadeExpansionChangeEvent;
 import com.android.systemui.shade.ShadeExpansionListener;
-import com.android.systemui.shade.ShadeExpansionStateManager;
-import com.android.systemui.statusbar.LockscreenShadeTransitionController;
 import com.android.systemui.statusbar.StatusBarState;
-import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
-import com.android.systemui.statusbar.phone.SystemUIDialogManager;
-import com.android.systemui.statusbar.phone.UnlockedScreenOffAnimationController;
-import com.android.systemui.statusbar.policy.ConfigurationController;
-import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.util.concurrency.DelayableExecutor;
-import com.android.systemui.util.time.FakeSystemClock;
+import com.android.systemui.statusbar.phone.KeyguardBouncer;
 
-import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.List;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @RunWithLooper
-public class UdfpsKeyguardViewControllerTest extends SysuiTestCase {
-    // Dependencies
-    @Mock
-    private UdfpsKeyguardView mView;
-    @Mock
-    private Context mResourceContext;
-    @Mock
-    private StatusBarStateController mStatusBarStateController;
-    @Mock
-    private ShadeExpansionStateManager mShadeExpansionStateManager;
-    @Mock
-    private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
-    @Mock
-    private LockscreenShadeTransitionController mLockscreenShadeTransitionController;
-    @Mock
-    private DumpManager mDumpManager;
-    @Mock
-    private DelayableExecutor mExecutor;
-    @Mock
-    private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
-    @Mock
-    private KeyguardStateController mKeyguardStateController;
-    @Mock
-    private KeyguardViewMediator mKeyguardViewMediator;
-    @Mock
-    private ConfigurationController mConfigurationController;
-    @Mock
-    private UnlockedScreenOffAnimationController mUnlockedScreenOffAnimationController;
-    @Mock
-    private SystemUIDialogManager mDialogManager;
-    @Mock
-    private UdfpsController mUdfpsController;
-    @Mock
-    private ActivityLaunchAnimator mActivityLaunchAnimator;
-    private FakeSystemClock mSystemClock = new FakeSystemClock();
+public class UdfpsKeyguardViewControllerTest extends UdfpsKeyguardViewControllerBaseTest {
+    private @Captor ArgumentCaptor<KeyguardBouncer.BouncerExpansionCallback>
+            mBouncerExpansionCallbackCaptor;
+    private KeyguardBouncer.BouncerExpansionCallback mBouncerExpansionCallback;
 
-    private UdfpsKeyguardViewController mController;
-
-    // Capture listeners so that they can be used to send events
-    @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerCaptor;
-    private StatusBarStateController.StateListener mStatusBarStateListener;
-
-    @Captor private ArgumentCaptor<ShadeExpansionListener> mExpansionListenerCaptor;
-    private List<ShadeExpansionListener> mExpansionListeners;
-
-    @Captor private ArgumentCaptor<StatusBarKeyguardViewManager.AlternateAuthInterceptor>
-            mAltAuthInterceptorCaptor;
-    private StatusBarKeyguardViewManager.AlternateAuthInterceptor mAltAuthInterceptor;
-
-    @Captor private ArgumentCaptor<KeyguardStateController.Callback>
-            mKeyguardStateControllerCallbackCaptor;
-    private KeyguardStateController.Callback mKeyguardStateControllerCallback;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-        when(mView.getContext()).thenReturn(mResourceContext);
-        when(mResourceContext.getString(anyInt())).thenReturn("test string");
-        when(mKeyguardViewMediator.isAnimatingScreenOff()).thenReturn(false);
-        when(mView.getUnpausedAlpha()).thenReturn(255);
-        mController = new UdfpsKeyguardViewController(
-                mView,
-                mStatusBarStateController,
-                mShadeExpansionStateManager,
-                mStatusBarKeyguardViewManager,
-                mKeyguardUpdateMonitor,
-                mDumpManager,
-                mLockscreenShadeTransitionController,
-                mConfigurationController,
-                mSystemClock,
-                mKeyguardStateController,
-                mUnlockedScreenOffAnimationController,
-                mDialogManager,
-                mUdfpsController,
-                mActivityLaunchAnimator);
+    @Override
+    public UdfpsKeyguardViewController createUdfpsKeyguardViewController() {
+        return createUdfpsKeyguardViewController(/* useModernBouncer */ false);
     }
 
     @Test
+    public void testShouldPauseAuth_bouncerShowing() {
+        mController.onViewAttached();
+        captureStatusBarStateListeners();
+        sendStatusBarStateChanged(StatusBarState.KEYGUARD);
+
+        captureBouncerExpansionCallback();
+        when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(true);
+        when(mStatusBarKeyguardViewManager.bouncerIsOrWillBeShowing()).thenReturn(true);
+        mBouncerExpansionCallback.onVisibilityChanged(true);
+
+        assertTrue(mController.shouldPauseAuth());
+    }
+
+
+
+    @Test
     public void testRegistersExpansionChangedListenerOnAttached() {
         mController.onViewAttached();
         captureStatusBarExpansionListeners();
@@ -202,20 +129,6 @@
     }
 
     @Test
-    public void testShouldPauseAuthBouncerShowing() {
-        mController.onViewAttached();
-        captureStatusBarStateListeners();
-        sendStatusBarStateChanged(StatusBarState.KEYGUARD);
-
-        captureAltAuthInterceptor();
-        when(mStatusBarKeyguardViewManager.isBouncerShowing()).thenReturn(true);
-        when(mStatusBarKeyguardViewManager.bouncerIsOrWillBeShowing()).thenReturn(true);
-        mAltAuthInterceptor.onBouncerVisibilityChanged();
-
-        assertTrue(mController.shouldPauseAuth());
-    }
-
-    @Test
     public void testShouldPauseAuthUnpausedAlpha0() {
         mController.onViewAttached();
         captureStatusBarStateListeners();
@@ -503,41 +416,8 @@
         verify(mView, atLeastOnce()).setPauseAuth(false);
     }
 
-    private void sendStatusBarStateChanged(int statusBarState) {
-        mStatusBarStateListener.onStateChanged(statusBarState);
-    }
-
-    private void captureStatusBarStateListeners() {
-        verify(mStatusBarStateController).addCallback(mStateListenerCaptor.capture());
-        mStatusBarStateListener = mStateListenerCaptor.getValue();
-    }
-
-    private void captureStatusBarExpansionListeners() {
-        verify(mShadeExpansionStateManager, times(2))
-                .addExpansionListener(mExpansionListenerCaptor.capture());
-        // first (index=0) is from super class, UdfpsAnimationViewController.
-        // second (index=1) is from UdfpsKeyguardViewController
-        mExpansionListeners = mExpansionListenerCaptor.getAllValues();
-    }
-
-    private void updateStatusBarExpansion(float fraction, boolean expanded) {
-        ShadeExpansionChangeEvent event =
-                new ShadeExpansionChangeEvent(
-                        fraction, expanded, /* tracking= */ false, /* dragDownPxAmount= */ 0f);
-        for (ShadeExpansionListener listener : mExpansionListeners) {
-            listener.onPanelExpansionChanged(event);
-        }
-    }
-
-    private void captureAltAuthInterceptor() {
-        verify(mStatusBarKeyguardViewManager).setAlternateAuthInterceptor(
-                mAltAuthInterceptorCaptor.capture());
-        mAltAuthInterceptor = mAltAuthInterceptorCaptor.getValue();
-    }
-
-    private void captureKeyguardStateControllerCallback() {
-        verify(mKeyguardStateController).addCallback(
-                mKeyguardStateControllerCallbackCaptor.capture());
-        mKeyguardStateControllerCallback = mKeyguardStateControllerCallbackCaptor.getValue();
+    private void captureBouncerExpansionCallback() {
+        verify(mBouncer).addBouncerExpansionCallback(mBouncerExpansionCallbackCaptor.capture());
+        mBouncerExpansionCallback = mBouncerExpansionCallbackCaptor.getValue();
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
new file mode 100644
index 0000000..7b19768
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerWithCoroutinesTest.kt
@@ -0,0 +1,102 @@
+/*
+ * Copyright (C) 2022 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.biometrics
+
+import android.os.Handler
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardSecurityModel
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.keyguard.DismissCallbackRegistry
+import com.android.systemui.keyguard.data.BouncerView
+import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
+import com.android.systemui.keyguard.domain.interactor.BouncerCallbackInteractor
+import com.android.systemui.keyguard.domain.interactor.BouncerInteractor
+import com.android.systemui.statusbar.StatusBarState
+import com.android.systemui.statusbar.phone.KeyguardBouncer
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.yield
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import org.mockito.MockitoAnnotations
+
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+@TestableLooper.RunWithLooper
+class UdfpsKeyguardViewControllerWithCoroutinesTest : UdfpsKeyguardViewControllerBaseTest() {
+    lateinit var keyguardBouncerRepository: KeyguardBouncerRepository
+
+    @Before
+    override fun setUp() {
+        allowTestableLooperAsMainThread() // repeatWhenAttached requires the main thread
+        MockitoAnnotations.initMocks(this)
+        keyguardBouncerRepository =
+            KeyguardBouncerRepository(
+                mock(com.android.keyguard.ViewMediatorCallback::class.java),
+                mKeyguardUpdateMonitor
+            )
+        super.setUp()
+    }
+
+    override fun createUdfpsKeyguardViewController(): UdfpsKeyguardViewController? {
+        mBouncerInteractor =
+            BouncerInteractor(
+                keyguardBouncerRepository,
+                mock(BouncerView::class.java),
+                mock(Handler::class.java),
+                mKeyguardStateController,
+                mock(KeyguardSecurityModel::class.java),
+                mock(BouncerCallbackInteractor::class.java),
+                mock(FalsingCollector::class.java),
+                mock(DismissCallbackRegistry::class.java),
+                mock(KeyguardBypassController::class.java),
+                mKeyguardUpdateMonitor
+            )
+        return createUdfpsKeyguardViewController(/* useModernBouncer */ true)
+    }
+
+    /** After migration, replaces LockIconViewControllerTest version */
+    @Test
+    fun testShouldPauseAuthBouncerShowing() =
+        runBlocking(IMMEDIATE) {
+            // GIVEN view attached and we're on the keyguard
+            mController.onViewAttached()
+            captureStatusBarStateListeners()
+            sendStatusBarStateChanged(StatusBarState.KEYGUARD)
+
+            // WHEN the bouncer expansion is VISIBLE
+            val job = mController.listenForBouncerExpansion(this)
+            keyguardBouncerRepository.setVisible(true)
+            keyguardBouncerRepository.setPanelExpansion(KeyguardBouncer.EXPANSION_VISIBLE)
+            yield()
+
+            // THEN UDFPS shouldPauseAuth == true
+            assertTrue(mController.shouldPauseAuth())
+
+            job.cancel()
+        }
+
+    companion object {
+        private val IMMEDIATE = Dispatchers.Main.immediate
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsShellTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsShellTest.kt
new file mode 100644
index 0000000..54f20db
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsShellTest.kt
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics
+
+import android.graphics.Rect
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.view.MotionEvent
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.UdfpsController.UdfpsOverlayController
+import com.android.systemui.statusbar.commandline.CommandRegistry
+import com.android.systemui.util.mockito.any
+import junit.framework.Assert.assertEquals
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenEver
+import org.mockito.junit.MockitoJUnit
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class UdfpsShellTest : SysuiTestCase() {
+
+    @JvmField @Rule var rule = MockitoJUnit.rule()
+
+    // Unit under test
+    private lateinit var udfpsShell: UdfpsShell
+
+    @Mock lateinit var commandRegistry: CommandRegistry
+    @Mock lateinit var udfpsOverlay: UdfpsOverlay
+    @Mock lateinit var udfpsOverlayController: UdfpsOverlayController
+
+    @Captor private lateinit var motionEvent: ArgumentCaptor<MotionEvent>
+
+    private val sensorBounds = Rect()
+
+    @Before
+    fun setup() {
+        whenEver(udfpsOverlayController.sensorBounds).thenReturn(sensorBounds)
+
+        udfpsShell = UdfpsShell(commandRegistry, udfpsOverlay)
+        udfpsShell.udfpsOverlayController = udfpsOverlayController
+    }
+
+    @Test
+    fun testSimFingerDown() {
+        udfpsShell.simFingerDown()
+
+        verify(udfpsOverlayController, times(2)).debugOnTouch(any(), motionEvent.capture())
+
+        assertEquals(motionEvent.allValues[0].action, MotionEvent.ACTION_DOWN) // ACTION_MOVE
+        assertEquals(motionEvent.allValues[1].action, MotionEvent.ACTION_MOVE) // ACTION_MOVE
+    }
+
+    @Test
+    fun testSimFingerUp() {
+        udfpsShell.simFingerUp()
+
+        verify(udfpsOverlayController).debugOnTouch(any(), motionEvent.capture())
+
+        assertEquals(motionEvent.value.action, MotionEvent.ACTION_UP)
+    }
+
+    @Test
+    fun testOnUiReady() {
+        udfpsShell.onUiReady()
+
+        verify(udfpsOverlayController).debugOnUiReady(any(), any())
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt
index 2eeff9f..4c5e3c1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt
@@ -1,6 +1,7 @@
 package com.android.systemui.biometrics.domain.model
 
 import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.promptInfo
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
@@ -12,7 +13,7 @@
 
 @SmallTest
 @RunWith(JUnit4::class)
-class BiometricPromptRequestTest {
+class BiometricPromptRequestTest : SysuiTestCase() {
 
     @Test
     fun biometricRequestFromPromptInfo() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
index 9481349..b811aab 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/BrightLineFalsingManagerTest.java
@@ -96,7 +96,6 @@
         assertThat(mBrightLineFalsingManager.isFalseTap(1)).isFalse();
     }
 
-
     @Test
     public void testA11yDisablesTap() {
         assertThat(mBrightLineFalsingManager.isFalseTouch(Classifier.GENERIC)).isTrue();
@@ -159,4 +158,11 @@
         });
         assertThat(mBrightLineFalsingManager.isProximityNear()).isFalse();
     }
+
+    @Test
+    public void testA11yAction() {
+        assertThat(mBrightLineFalsingManager.isFalseTap(1)).isTrue();
+        when(mFalsingDataProvider.isA11yAction()).thenReturn(true);
+        assertThat(mBrightLineFalsingManager.isFalseTap(1)).isFalse();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingA11yDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingA11yDelegateTest.kt
new file mode 100644
index 0000000..2c904e7
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingA11yDelegateTest.kt
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2022 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.classifier
+
+import android.testing.AndroidTestingRunner
+import android.view.View
+import android.view.accessibility.AccessibilityNodeInfo.ACTION_CLICK
+import android.view.accessibility.AccessibilityNodeInfo.ACTION_LONG_CLICK
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class FalsingA11yDelegateTest : SysuiTestCase() {
+    @Mock lateinit var falsingCollector: FalsingCollector
+    @Mock lateinit var view: View
+    lateinit var underTest: FalsingA11yDelegate
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        underTest = FalsingA11yDelegate(falsingCollector)
+    }
+
+    @Test
+    fun testPerformAccessibilityAction_ACTION_CLICK() {
+        underTest.performAccessibilityAction(view, ACTION_CLICK, null)
+        verify(falsingCollector).onA11yAction()
+    }
+
+    @Test
+    fun testPerformAccessibilityAction_not_ACTION_CLICK() {
+        underTest.performAccessibilityAction(view, ACTION_LONG_CLICK, null)
+        verify(falsingCollector, never()).onA11yAction()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
index fa9c41a..442bf91 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingCollectorImplTest.java
@@ -267,4 +267,10 @@
         mFalsingCollector.onTouchEvent(up);
         verify(mFalsingDataProvider, times(2)).onMotionEvent(any(MotionEvent.class));
     }
+
+    @Test
+    public void testOnA11yAction() {
+        mFalsingCollector.onA11yAction();
+        verify(mFalsingDataProvider).onA11yAction();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
index 5dc607f..d315c2d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/classifier/FalsingDataProviderTest.java
@@ -310,4 +310,10 @@
         // an empty array.
         assertThat(mDataProvider.getPriorMotionEvents()).isNotNull();
     }
+
+    @Test
+    public void test_MotionEventComplete_A11yAction() {
+        mDataProvider.onA11yAction();
+        assertThat(mDataProvider.isA11yAction()).isTrue();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
index b7f1c1a..a872e4b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayControllerTest.java
@@ -19,7 +19,9 @@
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISS_TAPPED;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SHARE_TAPPED;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SWIPE_DISMISSED;
+import static com.android.systemui.flags.Flags.CLIPBOARD_REMOTE_BEHAVIOR;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -37,6 +39,7 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.broadcast.BroadcastSender;
+import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.screenshot.TimeoutHandler;
 
 import org.junit.After;
@@ -62,7 +65,10 @@
     @Mock
     private TimeoutHandler mTimeoutHandler;
     @Mock
+    private ClipboardOverlayUtils mClipboardUtils;
+    @Mock
     private UiEventLogger mUiEventLogger;
+    private FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
 
     @Mock
     private Animator mAnimator;
@@ -73,7 +79,6 @@
     private ArgumentCaptor<ClipboardOverlayView.ClipboardOverlayCallbacks> mOverlayCallbacksCaptor;
     private ClipboardOverlayView.ClipboardOverlayCallbacks mCallbacks;
 
-
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
@@ -84,6 +89,8 @@
         mSampleClipData = new ClipData("Test", new String[]{"text/plain"},
                 new ClipData.Item("Test Item"));
 
+        mFeatureFlags.set(CLIPBOARD_REMOTE_BEHAVIOR, false);
+
         mOverlayController = new ClipboardOverlayController(
                 mContext,
                 mClipboardOverlayView,
@@ -91,6 +98,8 @@
                 getFakeBroadcastDispatcher(),
                 mBroadcastSender,
                 mTimeoutHandler,
+                mFeatureFlags,
+                mClipboardUtils,
                 mUiEventLogger);
         verify(mClipboardOverlayView).setCallbacks(mOverlayCallbacksCaptor.capture());
         mCallbacks = mOverlayCallbacksCaptor.getValue();
@@ -186,4 +195,33 @@
         verify(mUiEventLogger, times(1)).log(CLIPBOARD_OVERLAY_SWIPE_DISMISSED);
         verify(mUiEventLogger, never()).log(CLIPBOARD_OVERLAY_DISMISS_TAPPED);
     }
+
+    @Test
+    public void test_remoteCopy_withFlagOn() {
+        mFeatureFlags.set(CLIPBOARD_REMOTE_BEHAVIOR, true);
+        when(mClipboardUtils.isRemoteCopy(any(), any(), any())).thenReturn(true);
+
+        mOverlayController.setClipData(mSampleClipData, "");
+
+        verify(mTimeoutHandler, never()).resetTimeout();
+    }
+
+    @Test
+    public void test_remoteCopy_withFlagOff() {
+        when(mClipboardUtils.isRemoteCopy(any(), any(), any())).thenReturn(true);
+
+        mOverlayController.setClipData(mSampleClipData, "");
+
+        verify(mTimeoutHandler).resetTimeout();
+    }
+
+    @Test
+    public void test_nonRemoteCopy() {
+        mFeatureFlags.set(CLIPBOARD_REMOTE_BEHAVIOR, true);
+        when(mClipboardUtils.isRemoteCopy(any(), any(), any())).thenReturn(false);
+
+        mOverlayController.setClipData(mSampleClipData, "");
+
+        verify(mTimeoutHandler).resetTimeout();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtilsTest.java
new file mode 100644
index 0000000..09b1699
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/clipboardoverlay/ClipboardOverlayUtilsTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2022 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.clipboardoverlay;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.content.ClipData;
+import android.content.ClipDescription;
+import android.os.PersistableBundle;
+import android.testing.TestableResources;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.systemui.R;
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class ClipboardOverlayUtilsTest extends SysuiTestCase {
+
+    private ClipboardOverlayUtils mClipboardUtils;
+
+    @Before
+    public void setUp() {
+        mClipboardUtils = new ClipboardOverlayUtils();
+    }
+
+    @Test
+    public void test_extra_withPackage_returnsTrue() {
+        PersistableBundle b = new PersistableBundle();
+        b.putBoolean(ClipDescription.EXTRA_IS_REMOTE_DEVICE, true);
+        ClipData data = constructClipData(
+                new String[]{"text/plain"}, new ClipData.Item("6175550000"), b);
+        TestableResources res = mContext.getOrCreateTestableResources();
+        res.addOverride(
+                R.string.config_remoteCopyPackage, "com.android.remote/.RemoteActivity");
+
+        assertTrue(mClipboardUtils.isRemoteCopy(mContext, data, "com.android.remote"));
+    }
+
+    @Test
+    public void test_noExtra_returnsFalse() {
+        ClipData data = constructClipData(
+                new String[]{"text/plain"}, new ClipData.Item("6175550000"), null);
+        TestableResources res = mContext.getOrCreateTestableResources();
+        res.addOverride(
+                R.string.config_remoteCopyPackage, "com.android.remote/.RemoteActivity");
+
+        assertFalse(mClipboardUtils.isRemoteCopy(mContext, data, "com.android.remote"));
+    }
+
+    @Test
+    public void test_falseExtra_returnsFalse() {
+        PersistableBundle b = new PersistableBundle();
+        b.putBoolean(ClipDescription.EXTRA_IS_REMOTE_DEVICE, false);
+        ClipData data = constructClipData(
+                new String[]{"text/plain"}, new ClipData.Item("6175550000"), b);
+        TestableResources res = mContext.getOrCreateTestableResources();
+        res.addOverride(
+                R.string.config_remoteCopyPackage, "com.android.remote/.RemoteActivity");
+
+        assertFalse(mClipboardUtils.isRemoteCopy(mContext, data, "com.android.remote"));
+    }
+
+    @Test
+    public void test_wrongPackage_returnsFalse() {
+        PersistableBundle b = new PersistableBundle();
+        b.putBoolean(ClipDescription.EXTRA_IS_REMOTE_DEVICE, true);
+        ClipData data = constructClipData(
+                new String[]{"text/plain"}, new ClipData.Item("6175550000"), b);
+
+        assertFalse(mClipboardUtils.isRemoteCopy(mContext, data, ""));
+    }
+
+    static ClipData constructClipData(String[] mimeTypes, ClipData.Item item,
+            PersistableBundle extras) {
+        ClipDescription description = new ClipDescription("Test", mimeTypes);
+        if (extras != null) {
+            description.setExtras(extras);
+        }
+        return new ClipData(description, item);
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
new file mode 100644
index 0000000..49c7442
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
@@ -0,0 +1,155 @@
+/*
+ * Copyright (C) 2022 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.controls.ui
+
+import android.content.ComponentName
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.controls.ControlsMetricsLogger
+import com.android.systemui.controls.CustomIconCache
+import com.android.systemui.controls.controller.ControlsController
+import com.android.systemui.controls.controller.StructureInfo
+import com.android.systemui.controls.management.ControlsListingController
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserTracker
+import com.android.systemui.shade.ShadeController
+import com.android.systemui.statusbar.policy.DeviceControlsControllerImpl
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.FakeSharedPreferences
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import dagger.Lazy
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.anyInt
+import org.mockito.Mockito.anyString
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.times
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class ControlsUiControllerImplTest : SysuiTestCase() {
+    @Mock lateinit var controlsController: ControlsController
+    @Mock lateinit var controlsListingController: ControlsListingController
+    @Mock lateinit var controlActionCoordinator: ControlActionCoordinator
+    @Mock lateinit var activityStarter: ActivityStarter
+    @Mock lateinit var shadeController: ShadeController
+    @Mock lateinit var iconCache: CustomIconCache
+    @Mock lateinit var controlsMetricsLogger: ControlsMetricsLogger
+    @Mock lateinit var keyguardStateController: KeyguardStateController
+    @Mock lateinit var userFileManager: UserFileManager
+    @Mock lateinit var userTracker: UserTracker
+    val sharedPreferences = FakeSharedPreferences()
+
+    var uiExecutor = FakeExecutor(FakeSystemClock())
+    var bgExecutor = FakeExecutor(FakeSystemClock())
+    lateinit var underTest: ControlsUiControllerImpl
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+
+        underTest =
+            ControlsUiControllerImpl(
+                Lazy { controlsController },
+                context,
+                uiExecutor,
+                bgExecutor,
+                Lazy { controlsListingController },
+                controlActionCoordinator,
+                activityStarter,
+                shadeController,
+                iconCache,
+                controlsMetricsLogger,
+                keyguardStateController,
+                userFileManager,
+                userTracker
+            )
+        `when`(
+                userFileManager.getSharedPreferences(
+                    DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
+                    0,
+                    0
+                )
+            )
+            .thenReturn(sharedPreferences)
+        `when`(userFileManager.getSharedPreferences(anyString(), anyInt(), anyInt()))
+            .thenReturn(sharedPreferences)
+        `when`(userTracker.userId).thenReturn(0)
+    }
+
+    @Test
+    fun testGetPreferredStructure() {
+        val structureInfo = mock(StructureInfo::class.java)
+        underTest.getPreferredStructure(listOf(structureInfo))
+        verify(userFileManager, times(2))
+            .getSharedPreferences(
+                fileName = DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
+                mode = 0,
+                userId = 0
+            )
+    }
+
+    @Test
+    fun testGetPreferredStructure_differentUserId() {
+        val structureInfo =
+            listOf(
+                StructureInfo(ComponentName.unflattenFromString("pkg/.cls1"), "a", ArrayList()),
+                StructureInfo(ComponentName.unflattenFromString("pkg/.cls2"), "b", ArrayList()),
+            )
+        sharedPreferences
+            .edit()
+            .putString("controls_component", structureInfo[0].componentName.flattenToString())
+            .putString("controls_structure", structureInfo[0].structure.toString())
+            .commit()
+
+        val differentSharedPreferences = FakeSharedPreferences()
+        differentSharedPreferences
+            .edit()
+            .putString("controls_component", structureInfo[1].componentName.flattenToString())
+            .putString("controls_structure", structureInfo[1].structure.toString())
+            .commit()
+
+        val previousPreferredStructure = underTest.getPreferredStructure(structureInfo)
+
+        `when`(
+                userFileManager.getSharedPreferences(
+                    DeviceControlsControllerImpl.PREFS_CONTROLS_FILE,
+                    0,
+                    1
+                )
+            )
+            .thenReturn(differentSharedPreferences)
+        `when`(userTracker.userId).thenReturn(1)
+
+        val currentPreferredStructure = underTest.getPreferredStructure(structureInfo)
+
+        assertThat(previousPreferredStructure).isEqualTo(structureInfo[0])
+        assertThat(currentPreferredStructure).isEqualTo(structureInfo[1])
+        assertThat(currentPreferredStructure).isNotEqualTo(previousPreferredStructure)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
index c5a7de4..c234178 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayContainerViewControllerTest.java
@@ -92,6 +92,12 @@
     @Mock
     BouncerCallbackInteractor mBouncerCallbackInteractor;
 
+    @Mock
+    DreamOverlayAnimationsController mAnimationsController;
+
+    @Mock
+    DreamOverlayStateController mStateController;
+
     DreamOverlayContainerViewController mController;
 
     @Before
@@ -115,7 +121,9 @@
                 MAX_BURN_IN_OFFSET,
                 BURN_IN_PROTECTION_UPDATE_INTERVAL,
                 MILLIS_UNTIL_FULL_JITTER,
-                mBouncerCallbackInteractor);
+                mBouncerCallbackInteractor,
+                mAnimationsController,
+                mStateController);
     }
 
     @Test
@@ -188,4 +196,31 @@
         verify(mBlurUtils).blurRadiusOfRatio(1 - scaledFraction);
         verify(mBlurUtils).applyBlur(mViewRoot, (int) blurRadius, false);
     }
+
+    @Test
+    public void testStartDreamEntryAnimationsOnAttachedNonLowLight() {
+        when(mStateController.isLowLightActive()).thenReturn(false);
+
+        mController.onViewAttached();
+
+        verify(mAnimationsController).startEntryAnimations(mDreamOverlayContainerView);
+        verify(mAnimationsController, never()).cancelRunningEntryAnimations();
+    }
+
+    @Test
+    public void testNeverStartDreamEntryAnimationsOnAttachedForLowLight() {
+        when(mStateController.isLowLightActive()).thenReturn(true);
+
+        mController.onViewAttached();
+
+        verify(mAnimationsController, never()).startEntryAnimations(mDreamOverlayContainerView);
+    }
+
+    @Test
+    public void testCancelDreamEntryAnimationsOnDetached() {
+        mController.onViewAttached();
+        mController.onViewDetached();
+
+        verify(mAnimationsController).cancelRunningEntryAnimations();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
index f370be1..f04a37f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayServiceTest.java
@@ -253,6 +253,7 @@
         verify(mLifecycleRegistry).setCurrentState(Lifecycle.State.DESTROYED);
         verify(mStateController).setOverlayActive(false);
         verify(mStateController).setLowLightActive(false);
+        verify(mStateController).setEntryAnimationsFinished(false);
     }
 
     @Test
@@ -273,27 +274,31 @@
 
     @Test
     public void testDecorViewNotAddedToWindowAfterDestroy() throws Exception {
-        when(mDreamOverlayContainerView.getParent())
-                .thenReturn(mDreamOverlayContainerViewParent)
-                .thenReturn(null);
-
         final IBinder proxy = mService.onBind(new Intent());
         final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
 
+        // Destroy the service.
+        mService.onDestroy();
+        mMainExecutor.runAllReady();
+
         // Inform the overlay service of dream starting.
         overlay.startDream(mWindowParams, mDreamOverlayCallback, DREAM_COMPONENT,
                 false /*shouldShowComplication*/);
-
-        // Destroy the service.
-        mService.onDestroy();
-
-        // Run executor tasks.
         mMainExecutor.runAllReady();
 
         verify(mWindowManager, never()).addView(any(), any());
     }
 
     @Test
+    public void testNeverRemoveDecorViewIfNotAdded() {
+        // Service destroyed before dream started.
+        mService.onDestroy();
+        mMainExecutor.runAllReady();
+
+        verify(mWindowManager, never()).removeView(any());
+    }
+
+    @Test
     public void testResetCurrentOverlayWhenConnectedToNewDream() throws RemoteException {
         final IBinder proxy = mService.onBind(new Intent());
         final IDreamOverlay overlay = IDreamOverlay.Stub.asInterface(proxy);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
index d1d32a1..c21c7a2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStateControllerTest.java
@@ -234,4 +234,20 @@
         verify(mCallback, times(1)).onStateChanged();
         assertThat(stateController.isLowLightActive()).isTrue();
     }
+
+    @Test
+    public void testNotifyEntryAnimationsFinishedChanged() {
+        final DreamOverlayStateController stateController =
+                new DreamOverlayStateController(mExecutor);
+
+        stateController.addCallback(mCallback);
+        mExecutor.runAllReady();
+        assertThat(stateController.areEntryAnimationsFinished()).isFalse();
+
+        stateController.setEntryAnimationsFinished(true);
+        mExecutor.runAllReady();
+
+        verify(mCallback, times(1)).onStateChanged();
+        assertThat(stateController.areEntryAnimationsFinished()).isTrue();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
index aa02178..85c2819 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/DreamOverlayStatusBarViewControllerTest.java
@@ -19,16 +19,20 @@
 import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
 import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.doCallRealMethod;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.app.AlarmManager;
+import android.content.Context;
 import android.content.res.Resources;
 import android.hardware.SensorPrivacyManager;
 import android.net.ConnectivityManager;
@@ -55,6 +59,7 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
@@ -69,7 +74,7 @@
             "{count, plural, =1 {# notification} other {# notifications}}";
 
     @Mock
-    DreamOverlayStatusBarView mView;
+    MockDreamOverlayStatusBarView mView;
     @Mock
     ConnectivityManager mConnectivityManager;
     @Mock
@@ -105,6 +110,9 @@
     @Mock
     DreamOverlayStateController mDreamOverlayStateController;
 
+    @Captor
+    private ArgumentCaptor<DreamOverlayStateController.Callback> mCallbackCaptor;
+
     private final Executor mMainExecutor = Runnable::run;
 
     DreamOverlayStatusBarViewController mController;
@@ -115,6 +123,8 @@
 
         when(mResources.getString(R.string.dream_overlay_status_bar_notification_indicator))
                 .thenReturn(NOTIFICATION_INDICATOR_FORMATTER_STRING);
+        doCallRealMethod().when(mView).setVisibility(anyInt());
+        doCallRealMethod().when(mView).getVisibility();
 
         mController = new DreamOverlayStatusBarViewController(
                 mView,
@@ -454,12 +464,10 @@
     public void testStatusBarHiddenWhenSystemStatusBarShown() {
         mController.onViewAttached();
 
-        final ArgumentCaptor<StatusBarWindowStateListener>
-                callbackCapture = ArgumentCaptor.forClass(StatusBarWindowStateListener.class);
-        verify(mStatusBarWindowStateController).addListener(callbackCapture.capture());
-        callbackCapture.getValue().onStatusBarWindowStateChanged(WINDOW_STATE_SHOWING);
+        updateEntryAnimationsFinished();
+        updateStatusBarWindowState(true);
 
-        verify(mView).setVisibility(View.INVISIBLE);
+        assertThat(mView.getVisibility()).isEqualTo(View.INVISIBLE);
     }
 
     @Test
@@ -467,29 +475,43 @@
         mController.onViewAttached();
         reset(mView);
 
-        final ArgumentCaptor<StatusBarWindowStateListener>
-                callbackCapture = ArgumentCaptor.forClass(StatusBarWindowStateListener.class);
-        verify(mStatusBarWindowStateController).addListener(callbackCapture.capture());
-        callbackCapture.getValue().onStatusBarWindowStateChanged(WINDOW_STATE_HIDDEN);
+        updateEntryAnimationsFinished();
+        updateStatusBarWindowState(false);
 
-        verify(mView).setVisibility(View.VISIBLE);
+        assertThat(mView.getVisibility()).isEqualTo(View.VISIBLE);
     }
 
     @Test
     public void testUnattachedStatusBarVisibilityUnchangedWhenSystemStatusBarHidden() {
         mController.onViewAttached();
+        updateEntryAnimationsFinished();
         mController.onViewDetached();
         reset(mView);
 
-        final ArgumentCaptor<StatusBarWindowStateListener>
-                callbackCapture = ArgumentCaptor.forClass(StatusBarWindowStateListener.class);
-        verify(mStatusBarWindowStateController).addListener(callbackCapture.capture());
-        callbackCapture.getValue().onStatusBarWindowStateChanged(WINDOW_STATE_SHOWING);
+        updateStatusBarWindowState(true);
 
         verify(mView, never()).setVisibility(anyInt());
     }
 
     @Test
+    public void testNoChangeToVisibilityBeforeDreamStartedWhenStatusBarHidden() {
+        mController.onViewAttached();
+
+        // Trigger status bar window state change.
+        final StatusBarWindowStateListener listener = updateStatusBarWindowState(false);
+
+        // Verify no visibility change because dream not started.
+        verify(mView, never()).setVisibility(anyInt());
+
+        // Dream entry animations finished.
+        updateEntryAnimationsFinished();
+
+        // Trigger another status bar window state change, and verify visibility change.
+        listener.onStatusBarWindowStateChanged(WINDOW_STATE_HIDDEN);
+        assertThat(mView.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
     public void testExtraStatusBarItemSetWhenItemsChange() {
         mController.onViewAttached();
         when(mStatusBarItem.getView()).thenReturn(mStatusBarItemView);
@@ -507,16 +529,75 @@
     public void testLowLightHidesStatusBar() {
         when(mDreamOverlayStateController.isLowLightActive()).thenReturn(true);
         mController.onViewAttached();
+        updateEntryAnimationsFinished();
 
-        verify(mView).setVisibility(View.INVISIBLE);
-        reset(mView);
-
-        when(mDreamOverlayStateController.isLowLightActive()).thenReturn(false);
         final ArgumentCaptor<DreamOverlayStateController.Callback> callbackCapture =
                 ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
         verify(mDreamOverlayStateController).addCallback(callbackCapture.capture());
         callbackCapture.getValue().onStateChanged();
 
-        verify(mView).setVisibility(View.VISIBLE);
+        assertThat(mView.getVisibility()).isEqualTo(View.INVISIBLE);
+        reset(mView);
+
+        when(mDreamOverlayStateController.isLowLightActive()).thenReturn(false);
+        callbackCapture.getValue().onStateChanged();
+
+        assertThat(mView.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    @Test
+    public void testNoChangeToVisibilityBeforeDreamStartedWhenLowLightStateChange() {
+        when(mDreamOverlayStateController.isLowLightActive()).thenReturn(false);
+        mController.onViewAttached();
+
+        // No change to visibility because dream not fully started.
+        verify(mView, never()).setVisibility(anyInt());
+
+        // Dream entry animations finished.
+        updateEntryAnimationsFinished();
+
+        // Trigger state change and verify visibility changed.
+        final ArgumentCaptor<DreamOverlayStateController.Callback> callbackCapture =
+                ArgumentCaptor.forClass(DreamOverlayStateController.Callback.class);
+        verify(mDreamOverlayStateController).addCallback(callbackCapture.capture());
+        callbackCapture.getValue().onStateChanged();
+
+        assertThat(mView.getVisibility()).isEqualTo(View.VISIBLE);
+    }
+
+    private StatusBarWindowStateListener updateStatusBarWindowState(boolean show) {
+        when(mStatusBarWindowStateController.windowIsShowing()).thenReturn(show);
+        final ArgumentCaptor<StatusBarWindowStateListener>
+                callbackCapture = ArgumentCaptor.forClass(StatusBarWindowStateListener.class);
+        verify(mStatusBarWindowStateController).addListener(callbackCapture.capture());
+        final StatusBarWindowStateListener listener = callbackCapture.getValue();
+        listener.onStatusBarWindowStateChanged(show ? WINDOW_STATE_SHOWING : WINDOW_STATE_HIDDEN);
+        return listener;
+    }
+
+    private void updateEntryAnimationsFinished() {
+        when(mDreamOverlayStateController.areEntryAnimationsFinished()).thenReturn(true);
+
+        verify(mDreamOverlayStateController).addCallback(mCallbackCaptor.capture());
+        final DreamOverlayStateController.Callback callback = mCallbackCaptor.getValue();
+        callback.onStateChanged();
+    }
+
+    private static class MockDreamOverlayStatusBarView extends DreamOverlayStatusBarView {
+        private int mVisibility = View.VISIBLE;
+
+        private MockDreamOverlayStatusBarView(Context context) {
+            super(context);
+        }
+
+        @Override
+        public void setVisibility(int visibility) {
+            mVisibility = visibility;
+        }
+
+        @Override
+        public int getVisibility() {
+            return mVisibility;
+        }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java
index 3b9e398..b477592 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/complication/ComplicationHostViewControllerTest.java
@@ -16,6 +16,7 @@
 package com.android.systemui.dreams.complication;
 
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -29,16 +30,18 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.dreams.DreamOverlayStateController;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashSet;
 
 @SmallTest
@@ -77,9 +80,20 @@
     @Mock
     ComplicationLayoutParams mComplicationLayoutParams;
 
+    @Mock
+    DreamOverlayStateController mDreamOverlayStateController;
+
+    @Captor
+    private ArgumentCaptor<Observer<Collection<ComplicationViewModel>>> mObserverCaptor;
+
+    @Captor
+    private ArgumentCaptor<DreamOverlayStateController.Callback> mCallbackCaptor;
+
     @Complication.Category
     static final int COMPLICATION_CATEGORY = Complication.CATEGORY_SYSTEM;
 
+    private ComplicationHostViewController mController;
+
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
@@ -91,6 +105,15 @@
         when(mViewHolder.getCategory()).thenReturn(COMPLICATION_CATEGORY);
         when(mViewHolder.getLayoutParams()).thenReturn(mComplicationLayoutParams);
         when(mComplicationView.getParent()).thenReturn(mComplicationHostView);
+
+        mController = new ComplicationHostViewController(
+                mComplicationHostView,
+                mLayoutEngine,
+                mDreamOverlayStateController,
+                mLifecycleOwner,
+                mViewModel);
+
+        mController.init();
     }
 
     /**
@@ -98,25 +121,12 @@
      */
     @Test
     public void testViewModelObservation() {
-        final ArgumentCaptor<Observer<Collection<ComplicationViewModel>>> observerArgumentCaptor =
-                ArgumentCaptor.forClass(Observer.class);
-        final ComplicationHostViewController controller = new ComplicationHostViewController(
-                mComplicationHostView,
-                mLayoutEngine,
-                mLifecycleOwner,
-                mViewModel);
-
-        controller.init();
-
-        verify(mComplicationViewModelLiveData).observe(eq(mLifecycleOwner),
-                observerArgumentCaptor.capture());
-
         final Observer<Collection<ComplicationViewModel>> observer =
-                observerArgumentCaptor.getValue();
+                captureComplicationViewModelsObserver();
 
-        // Add complication and ensure it is added to the view.
+        // Add a complication and ensure it is added to the view.
         final HashSet<ComplicationViewModel> complications = new HashSet<>(
-                Arrays.asList(mComplicationViewModel));
+                Collections.singletonList(mComplicationViewModel));
         observer.onChanged(complications);
 
         verify(mLayoutEngine).addComplication(eq(mComplicationId), eq(mComplicationView),
@@ -127,4 +137,48 @@
 
         verify(mLayoutEngine).removeComplication(eq(mComplicationId));
     }
+
+    @Test
+    public void testNewComplicationsBeforeEntryAnimationsFinishSetToInvisible() {
+        final Observer<Collection<ComplicationViewModel>> observer =
+                captureComplicationViewModelsObserver();
+
+        // Add a complication before entry animations are finished.
+        final HashSet<ComplicationViewModel> complications = new HashSet<>(
+                Collections.singletonList(mComplicationViewModel));
+        observer.onChanged(complications);
+
+        // The complication view should be set to invisible.
+        verify(mComplicationView).setVisibility(View.INVISIBLE);
+    }
+
+    @Test
+    public void testNewComplicationsAfterEntryAnimationsFinishNotSetToInvisible() {
+        final Observer<Collection<ComplicationViewModel>> observer =
+                captureComplicationViewModelsObserver();
+
+        // Dream entry animations finished.
+        when(mDreamOverlayStateController.areEntryAnimationsFinished()).thenReturn(true);
+        final DreamOverlayStateController.Callback stateCallback = captureOverlayStateCallback();
+        stateCallback.onStateChanged();
+
+        // Add a complication after entry animations are finished.
+        final HashSet<ComplicationViewModel> complications = new HashSet<>(
+                Collections.singletonList(mComplicationViewModel));
+        observer.onChanged(complications);
+
+        // The complication view should not be set to invisible.
+        verify(mComplicationView, never()).setVisibility(View.INVISIBLE);
+    }
+
+    private Observer<Collection<ComplicationViewModel>> captureComplicationViewModelsObserver() {
+        verify(mComplicationViewModelLiveData).observe(eq(mLifecycleOwner),
+                mObserverCaptor.capture());
+        return mObserverCaptor.getValue();
+    }
+
+    private DreamOverlayStateController.Callback captureOverlayStateCallback() {
+        verify(mDreamOverlayStateController).addCallback(mCallbackCaptor.capture());
+        return mCallbackCaptor.getValue();
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
index 20a82c6..4b3b70e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsDebugTest.kt
@@ -88,6 +88,7 @@
             flagMap,
             restarter
         )
+        mFeatureFlagsDebug.init()
         verify(flagManager).onSettingsChangedAction = any()
         broadcastReceiver = withArgCaptor {
             verify(mockContext).registerReceiver(capture(), any(), nullable(), nullable(),
@@ -255,11 +256,11 @@
         broadcastReceiver.onReceive(mockContext, Intent())
         broadcastReceiver.onReceive(mockContext, Intent("invalid action"))
         broadcastReceiver.onReceive(mockContext, Intent(FlagManager.ACTION_SET_FLAG))
-        setByBroadcast(0, false)     // unknown id does nothing
-        setByBroadcast(1, "string")  // wrong type does nothing
-        setByBroadcast(2, 123)       // wrong type does nothing
-        setByBroadcast(3, false)     // wrong type does nothing
-        setByBroadcast(4, 123)       // wrong type does nothing
+        setByBroadcast(0, false) // unknown id does nothing
+        setByBroadcast(1, "string") // wrong type does nothing
+        setByBroadcast(2, 123) // wrong type does nothing
+        setByBroadcast(3, false) // wrong type does nothing
+        setByBroadcast(4, 123) // wrong type does nothing
         verifyNoMoreInteractions(flagManager, secureSettings)
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
index 575c142..b2dd60c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/FeatureFlagsReleaseTest.kt
@@ -38,8 +38,9 @@
 
     @Mock private lateinit var mResources: Resources
     @Mock private lateinit var mSystemProperties: SystemPropertiesHelper
+    @Mock private lateinit var restarter: Restarter
+    private val flagMap = mutableMapOf<Int, Flag<*>>()
     private val serverFlagReader = ServerFlagReaderFake()
-
     private val deviceConfig = DeviceConfigProxyFake()
 
     @Before
@@ -49,7 +50,9 @@
             mResources,
             mSystemProperties,
             deviceConfig,
-            serverFlagReader)
+            serverFlagReader,
+            flagMap,
+            restarter)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt
new file mode 100644
index 0000000..6f5f460
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/flags/ServerFlagReaderImplTest.kt
@@ -0,0 +1,61 @@
+/*
+ * Copyright (C) 2022 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.flags
+
+import android.test.suitebuilder.annotation.SmallTest
+import android.testing.AndroidTestingRunner
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.DeviceConfigProxyFake
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ServerFlagReaderImplTest : SysuiTestCase() {
+
+    private val NAMESPACE = "test"
+
+    @Mock private lateinit var changeListener: ServerFlagReader.ChangeListener
+
+    private lateinit var serverFlagReader: ServerFlagReaderImpl
+    private val deviceConfig = DeviceConfigProxyFake()
+    private val executor = FakeExecutor(FakeSystemClock())
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+
+        serverFlagReader = ServerFlagReaderImpl(NAMESPACE, deviceConfig, executor)
+    }
+
+    @Test
+    fun testChange_alertsListener() {
+        val flag = ReleasedFlag(1)
+        serverFlagReader.listenForChanges(listOf(flag), changeListener)
+
+        deviceConfig.setProperty(NAMESPACE, "flag_override_1", "1", false)
+        executor.runAllReady()
+
+        verify(changeListener).onChange()
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityTest.java
deleted file mode 100644
index 640e6dc..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityTest.java
+++ /dev/null
@@ -1,106 +0,0 @@
-/*
- * Copyright (C) 2017 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.keyguard;
-
-import static org.junit.Assert.assertEquals;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.Mockito.eq;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.annotation.UserIdInt;
-import android.content.Context;
-import android.content.Intent;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageManager;
-import android.graphics.drawable.Drawable;
-import android.os.Looper;
-import android.os.UserHandle;
-import android.os.UserManager;
-
-import androidx.test.filters.SmallTest;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-/**
- * runtest systemui -c com.android.systemui.keyguard.WorkLockActivityTest
- */
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class WorkLockActivityTest extends SysuiTestCase {
-    private static final @UserIdInt int USER_ID = 270;
-    private static final String CALLING_PACKAGE_NAME = "com.android.test";
-
-    private @Mock UserManager mUserManager;
-    private @Mock PackageManager mPackageManager;
-    private @Mock Context mContext;
-    private @Mock BroadcastDispatcher mBroadcastDispatcher;
-    private @Mock Drawable mDrawable;
-    private @Mock Drawable mBadgedDrawable;
-
-    private WorkLockActivity mActivity;
-
-    private static class WorkLockActivityTestable extends WorkLockActivity {
-        WorkLockActivityTestable(Context baseContext, BroadcastDispatcher broadcastDispatcher,
-                UserManager userManager, PackageManager packageManager) {
-            super(broadcastDispatcher, userManager, packageManager);
-            attachBaseContext(baseContext);
-        }
-    }
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-
-        if (Looper.myLooper() == null) {
-            Looper.prepare();
-        }
-        mActivity = new WorkLockActivityTestable(mContext, mBroadcastDispatcher, mUserManager,
-                mPackageManager);
-    }
-
-    @Test
-    public void testGetBadgedIcon() throws Exception {
-        ApplicationInfo info = new ApplicationInfo();
-        when(mPackageManager.getApplicationInfoAsUser(eq(CALLING_PACKAGE_NAME), any(),
-                eq(USER_ID))).thenReturn(info);
-        when(mPackageManager.getApplicationIcon(eq(info))).thenReturn(mDrawable);
-        when(mUserManager.getBadgedIconForUser(any(), eq(UserHandle.of(USER_ID)))).thenReturn(
-                mBadgedDrawable);
-        mActivity.setIntent(new Intent()
-                .putExtra(Intent.EXTRA_USER_ID, USER_ID)
-                .putExtra(Intent.EXTRA_PACKAGE_NAME, CALLING_PACKAGE_NAME));
-
-        assertEquals(mBadgedDrawable, mActivity.getBadgedIcon());
-    }
-
-    @Test
-    public void testUnregisteredFromDispatcher() {
-        mActivity.unregisterBroadcastReceiver();
-        verify(mBroadcastDispatcher).unregisterReceiver(any());
-        verify(mContext, never()).unregisterReceiver(any());
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityTest.kt
new file mode 100644
index 0000000..c7f1dec
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/WorkLockActivityTest.kt
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2017 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.keyguard
+
+import android.annotation.UserIdInt
+import android.content.Context
+import android.content.Intent
+import android.content.pm.ApplicationInfo
+import android.content.pm.PackageManager
+import android.content.pm.PackageManager.ApplicationInfoFlags
+import android.graphics.drawable.Drawable
+import android.os.Looper
+import android.os.UserHandle
+import android.os.UserManager
+import androidx.test.filters.SmallTest
+import androidx.test.runner.AndroidJUnit4
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.nullable
+import com.android.systemui.util.mockito.whenever
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+/** runtest systemui -c com.android.systemui.keyguard.WorkLockActivityTest */
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class WorkLockActivityTest : SysuiTestCase() {
+    private val context: Context = mock()
+    private val userManager: UserManager = mock()
+    private val packageManager: PackageManager = mock()
+    private val broadcastDispatcher: BroadcastDispatcher = mock()
+    private val drawable: Drawable = mock()
+    private val badgedDrawable: Drawable = mock()
+    private lateinit var activity: WorkLockActivity
+
+    private class WorkLockActivityTestable
+    constructor(
+        baseContext: Context,
+        broadcastDispatcher: BroadcastDispatcher,
+        userManager: UserManager,
+        packageManager: PackageManager,
+    ) : WorkLockActivity(broadcastDispatcher, userManager, packageManager) {
+        init {
+            attachBaseContext(baseContext)
+        }
+    }
+
+    @Before
+    fun setUp() {
+        if (Looper.myLooper() == null) {
+            Looper.prepare()
+        }
+        activity =
+            WorkLockActivityTestable(
+                baseContext = context,
+                broadcastDispatcher = broadcastDispatcher,
+                userManager = userManager,
+                packageManager = packageManager
+            )
+    }
+
+    @Test
+    fun testGetBadgedIcon() {
+        val info = ApplicationInfo()
+        whenever(
+                packageManager.getApplicationInfoAsUser(
+                    eq(CALLING_PACKAGE_NAME),
+                    any<ApplicationInfoFlags>(),
+                    eq(USER_ID)
+                )
+            )
+            .thenReturn(info)
+        whenever(packageManager.getApplicationIcon(ArgumentMatchers.eq(info))).thenReturn(drawable)
+        whenever(userManager.getBadgedIconForUser(any(), eq(UserHandle.of(USER_ID))))
+            .thenReturn(badgedDrawable)
+        activity.intent =
+            Intent()
+                .putExtra(Intent.EXTRA_USER_ID, USER_ID)
+                .putExtra(Intent.EXTRA_PACKAGE_NAME, CALLING_PACKAGE_NAME)
+        assertEquals(badgedDrawable, activity.badgedIcon)
+    }
+
+    @Test
+    fun testUnregisteredFromDispatcher() {
+        activity.unregisterBroadcastReceiver()
+        verify(broadcastDispatcher).unregisterReceiver(any())
+        verify(context, never()).unregisterReceiver(nullable())
+    }
+
+    @Test
+    fun onBackPressed_finishActivity() {
+        assertFalse(activity.isFinishing)
+
+        activity.onBackPressed()
+
+        assertFalse(activity.isFinishing)
+    }
+
+    companion object {
+        @UserIdInt private val USER_ID = 270
+        private const val CALLING_PACKAGE_NAME = "com.android.test"
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
index ce11008..0fb181d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/FakeKeyguardQuickAffordanceConfig.kt
@@ -18,37 +18,35 @@
 package com.android.systemui.keyguard.data.quickaffordance
 
 import com.android.systemui.animation.Expandable
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnTriggeredResult
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.yield
 
-/**
- * Fake implementation of a quick affordance data source.
- *
- * This class is abstract to force tests to provide extensions of it as the system that references
- * these configs uses each implementation's class type to refer to them.
- */
-abstract class FakeKeyguardQuickAffordanceConfig(
+/** Fake implementation of a quick affordance data source. */
+class FakeKeyguardQuickAffordanceConfig(
     override val key: String,
+    override val pickerName: String = key,
+    override val pickerIconResourceId: Int = 0,
 ) : KeyguardQuickAffordanceConfig {
 
-    var onClickedResult: OnClickedResult = OnClickedResult.Handled
+    var onTriggeredResult: OnTriggeredResult = OnTriggeredResult.Handled
 
-    private val _state =
-        MutableStateFlow<KeyguardQuickAffordanceConfig.State>(
-            KeyguardQuickAffordanceConfig.State.Hidden
+    private val _lockScreenState =
+        MutableStateFlow<KeyguardQuickAffordanceConfig.LockScreenState>(
+            KeyguardQuickAffordanceConfig.LockScreenState.Hidden
         )
-    override val state: Flow<KeyguardQuickAffordanceConfig.State> = _state
+    override val lockScreenState: Flow<KeyguardQuickAffordanceConfig.LockScreenState> =
+        _lockScreenState
 
-    override fun onQuickAffordanceClicked(
+    override fun onTriggered(
         expandable: Expandable?,
-    ): OnClickedResult {
-        return onClickedResult
+    ): OnTriggeredResult {
+        return onTriggeredResult
     }
 
-    suspend fun setState(state: KeyguardQuickAffordanceConfig.State) {
-        _state.value = state
+    suspend fun setState(lockScreenState: KeyguardQuickAffordanceConfig.LockScreenState) {
+        _lockScreenState.value = lockScreenState
         // Yield to allow the test's collection coroutine to "catch up" and collect this value
         // before the test continues to the next line.
         // TODO(b/239834928): once coroutines.test is updated, switch to the approach described in
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
index b120303..c94cec6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
@@ -122,8 +122,8 @@
                     emptyList()
                 }
             )
-        val values = mutableListOf<KeyguardQuickAffordanceConfig.State>()
-        val job = underTest.state.onEach(values::add).launchIn(this)
+        val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>()
+        val job = underTest.lockScreenState.onEach(values::add).launchIn(this)
 
         if (canShowWhileLocked) {
             verify(controlsListingController).addCallback(callbackCaptor.capture())
@@ -139,9 +139,9 @@
         assertThat(values.last())
             .isInstanceOf(
                 if (isVisibleExpected) {
-                    KeyguardQuickAffordanceConfig.State.Visible::class.java
+                    KeyguardQuickAffordanceConfig.LockScreenState.Visible::class.java
                 } else {
-                    KeyguardQuickAffordanceConfig.State.Hidden::class.java
+                    KeyguardQuickAffordanceConfig.LockScreenState.Hidden::class.java
                 }
             )
         job.cancel()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
index ce8d36d..659c1e5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigTest.kt
@@ -23,7 +23,7 @@
 import com.android.systemui.animation.Expandable
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.controls.dagger.ControlsComponent
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnTriggeredResult
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
 import java.util.Optional
@@ -72,11 +72,11 @@
         whenever(component.getVisibility()).thenReturn(ControlsComponent.Visibility.AVAILABLE)
         whenever(controlsController.getFavorites()).thenReturn(listOf(mock()))
 
-        val values = mutableListOf<KeyguardQuickAffordanceConfig.State>()
-        val job = underTest.state.onEach(values::add).launchIn(this)
+        val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>()
+        val job = underTest.lockScreenState.onEach(values::add).launchIn(this)
 
         assertThat(values.last())
-            .isInstanceOf(KeyguardQuickAffordanceConfig.State.Hidden::class.java)
+            .isInstanceOf(KeyguardQuickAffordanceConfig.LockScreenState.Hidden::class.java)
         job.cancel()
     }
 
@@ -91,31 +91,32 @@
         whenever(component.getVisibility()).thenReturn(ControlsComponent.Visibility.AVAILABLE)
         whenever(controlsController.getFavorites()).thenReturn(listOf(mock()))
 
-        val values = mutableListOf<KeyguardQuickAffordanceConfig.State>()
-        val job = underTest.state.onEach(values::add).launchIn(this)
+        val values = mutableListOf<KeyguardQuickAffordanceConfig.LockScreenState>()
+        val job = underTest.lockScreenState.onEach(values::add).launchIn(this)
 
         assertThat(values.last())
-            .isInstanceOf(KeyguardQuickAffordanceConfig.State.Hidden::class.java)
+            .isInstanceOf(KeyguardQuickAffordanceConfig.LockScreenState.Hidden::class.java)
         job.cancel()
     }
 
     @Test
-    fun `onQuickAffordanceClicked - canShowWhileLockedSetting is true`() = runBlockingTest {
+    fun `onQuickAffordanceTriggered - canShowWhileLockedSetting is true`() = runBlockingTest {
         whenever(component.canShowWhileLockedSetting).thenReturn(MutableStateFlow(true))
 
-        val onClickedResult = underTest.onQuickAffordanceClicked(expandable)
+        val onClickedResult = underTest.onTriggered(expandable)
 
-        assertThat(onClickedResult).isInstanceOf(OnClickedResult.StartActivity::class.java)
-        assertThat((onClickedResult as OnClickedResult.StartActivity).canShowWhileLocked).isTrue()
+        assertThat(onClickedResult).isInstanceOf(OnTriggeredResult.StartActivity::class.java)
+        assertThat((onClickedResult as OnTriggeredResult.StartActivity).canShowWhileLocked).isTrue()
     }
 
     @Test
-    fun `onQuickAffordanceClicked - canShowWhileLockedSetting is false`() = runBlockingTest {
+    fun `onQuickAffordanceTriggered - canShowWhileLockedSetting is false`() = runBlockingTest {
         whenever(component.canShowWhileLockedSetting).thenReturn(MutableStateFlow(false))
 
-        val onClickedResult = underTest.onQuickAffordanceClicked(expandable)
+        val onClickedResult = underTest.onTriggered(expandable)
 
-        assertThat(onClickedResult).isInstanceOf(OnClickedResult.StartActivity::class.java)
-        assertThat((onClickedResult as OnClickedResult.StartActivity).canShowWhileLocked).isFalse()
+        assertThat(onClickedResult).isInstanceOf(OnTriggeredResult.StartActivity::class.java)
+        assertThat((onClickedResult as OnTriggeredResult.StartActivity).canShowWhileLocked)
+            .isFalse()
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt
new file mode 100644
index 0000000..d2422ad
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/KeyguardQuickAffordanceSelectionManagerTest.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2022 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.keyguard.data.quickaffordance
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardQuickAffordanceSelectionManagerTest : SysuiTestCase() {
+
+    private lateinit var underTest: KeyguardQuickAffordanceSelectionManager
+
+    @Before
+    fun setUp() {
+        underTest = KeyguardQuickAffordanceSelectionManager()
+    }
+
+    @Test
+    fun setSelections() =
+        runBlocking(IMMEDIATE) {
+            var affordanceIdsBySlotId: Map<String, List<String>>? = null
+            val job = underTest.selections.onEach { affordanceIdsBySlotId = it }.launchIn(this)
+            val slotId1 = "slot1"
+            val slotId2 = "slot2"
+            val affordanceId1 = "affordance1"
+            val affordanceId2 = "affordance2"
+            val affordanceId3 = "affordance3"
+
+            underTest.setSelections(
+                slotId = slotId1,
+                affordanceIds = listOf(affordanceId1),
+            )
+            assertSelections(
+                affordanceIdsBySlotId,
+                mapOf(
+                    slotId1 to listOf(affordanceId1),
+                ),
+            )
+
+            underTest.setSelections(
+                slotId = slotId2,
+                affordanceIds = listOf(affordanceId2),
+            )
+            assertSelections(
+                affordanceIdsBySlotId,
+                mapOf(
+                    slotId1 to listOf(affordanceId1),
+                    slotId2 to listOf(affordanceId2),
+                )
+            )
+
+            underTest.setSelections(
+                slotId = slotId1,
+                affordanceIds = listOf(affordanceId1, affordanceId3),
+            )
+            assertSelections(
+                affordanceIdsBySlotId,
+                mapOf(
+                    slotId1 to listOf(affordanceId1, affordanceId3),
+                    slotId2 to listOf(affordanceId2),
+                )
+            )
+
+            underTest.setSelections(
+                slotId = slotId1,
+                affordanceIds = listOf(affordanceId3),
+            )
+            assertSelections(
+                affordanceIdsBySlotId,
+                mapOf(
+                    slotId1 to listOf(affordanceId3),
+                    slotId2 to listOf(affordanceId2),
+                )
+            )
+
+            underTest.setSelections(
+                slotId = slotId2,
+                affordanceIds = listOf(),
+            )
+            assertSelections(
+                affordanceIdsBySlotId,
+                mapOf(
+                    slotId1 to listOf(affordanceId3),
+                    slotId2 to listOf(),
+                )
+            )
+
+            job.cancel()
+        }
+
+    private suspend fun assertSelections(
+        observed: Map<String, List<String>>?,
+        expected: Map<String, List<String>>,
+    ) {
+        assertThat(underTest.getSelections()).isEqualTo(expected)
+        assertThat(observed).isEqualTo(expected)
+    }
+
+    companion object {
+        private val IMMEDIATE = Dispatchers.Main.immediate
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
index 9346440..2bd8e9a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QrCodeScannerKeyguardQuickAffordanceConfigTest.kt
@@ -20,7 +20,7 @@
 import android.content.Intent
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnClickedResult
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig.OnTriggeredResult
 import com.android.systemui.qrcodescanner.controller.QRCodeScannerController
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.mock
@@ -50,15 +50,15 @@
         MockitoAnnotations.initMocks(this)
         whenever(controller.intent).thenReturn(INTENT_1)
 
-        underTest = QrCodeScannerKeyguardQuickAffordanceConfig(controller)
+        underTest = QrCodeScannerKeyguardQuickAffordanceConfig(mock(), controller)
     }
 
     @Test
     fun `affordance - sets up registration and delivers initial model`() = runBlockingTest {
         whenever(controller.isEnabledForLockScreenButton).thenReturn(true)
-        var latest: KeyguardQuickAffordanceConfig.State? = null
+        var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
 
-        val job = underTest.state.onEach { latest = it }.launchIn(this)
+        val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
 
         val callbackCaptor = argumentCaptor<QRCodeScannerController.Callback>()
         verify(controller).addCallback(callbackCaptor.capture())
@@ -77,8 +77,8 @@
     fun `affordance - scanner activity changed - delivers model with updated intent`() =
         runBlockingTest {
             whenever(controller.isEnabledForLockScreenButton).thenReturn(true)
-            var latest: KeyguardQuickAffordanceConfig.State? = null
-            val job = underTest.state.onEach { latest = it }.launchIn(this)
+            var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
+            val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
             val callbackCaptor = argumentCaptor<QRCodeScannerController.Callback>()
             verify(controller).addCallback(callbackCaptor.capture())
 
@@ -93,8 +93,8 @@
 
     @Test
     fun `affordance - scanner preference changed - delivers visible model`() = runBlockingTest {
-        var latest: KeyguardQuickAffordanceConfig.State? = null
-        val job = underTest.state.onEach { latest = it }.launchIn(this)
+        var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
+        val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
         val callbackCaptor = argumentCaptor<QRCodeScannerController.Callback>()
         verify(controller).addCallback(callbackCaptor.capture())
 
@@ -109,34 +109,35 @@
 
     @Test
     fun `affordance - scanner preference changed - delivers none`() = runBlockingTest {
-        var latest: KeyguardQuickAffordanceConfig.State? = null
-        val job = underTest.state.onEach { latest = it }.launchIn(this)
+        var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
+        val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
         val callbackCaptor = argumentCaptor<QRCodeScannerController.Callback>()
         verify(controller).addCallback(callbackCaptor.capture())
 
         whenever(controller.isEnabledForLockScreenButton).thenReturn(false)
         callbackCaptor.value.onQRCodeScannerPreferenceChanged()
 
-        assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden)
+        assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
 
         job.cancel()
         verify(controller).removeCallback(callbackCaptor.value)
     }
 
     @Test
-    fun onQuickAffordanceClicked() {
-        assertThat(underTest.onQuickAffordanceClicked(mock()))
+    fun onQuickAffordanceTriggered() {
+        assertThat(underTest.onTriggered(mock()))
             .isEqualTo(
-                OnClickedResult.StartActivity(
+                OnTriggeredResult.StartActivity(
                     intent = INTENT_1,
                     canShowWhileLocked = true,
                 )
             )
     }
 
-    private fun assertVisibleState(latest: KeyguardQuickAffordanceConfig.State?) {
-        assertThat(latest).isInstanceOf(KeyguardQuickAffordanceConfig.State.Visible::class.java)
-        val visibleState = latest as KeyguardQuickAffordanceConfig.State.Visible
+    private fun assertVisibleState(latest: KeyguardQuickAffordanceConfig.LockScreenState?) {
+        assertThat(latest)
+            .isInstanceOf(KeyguardQuickAffordanceConfig.LockScreenState.Visible::class.java)
+        val visibleState = latest as KeyguardQuickAffordanceConfig.LockScreenState.Visible
         assertThat(visibleState.icon).isNotNull()
         assertThat(visibleState.icon.contentDescription).isNotNull()
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
index ae9e3c7..5178154 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/QuickAccessWalletKeyguardQuickAffordanceConfigTest.kt
@@ -59,6 +59,7 @@
 
         underTest =
             QuickAccessWalletKeyguardQuickAffordanceConfig(
+                mock(),
                 walletController,
                 activityStarter,
             )
@@ -67,11 +68,11 @@
     @Test
     fun `affordance - keyguard showing - has wallet card - visible model`() = runBlockingTest {
         setUpState()
-        var latest: KeyguardQuickAffordanceConfig.State? = null
+        var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
 
-        val job = underTest.state.onEach { latest = it }.launchIn(this)
+        val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
 
-        val visibleModel = latest as KeyguardQuickAffordanceConfig.State.Visible
+        val visibleModel = latest as KeyguardQuickAffordanceConfig.LockScreenState.Visible
         assertThat(visibleModel.icon)
             .isEqualTo(
                 Icon.Loaded(
@@ -88,11 +89,11 @@
     @Test
     fun `affordance - wallet not enabled - model is none`() = runBlockingTest {
         setUpState(isWalletEnabled = false)
-        var latest: KeyguardQuickAffordanceConfig.State? = null
+        var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
 
-        val job = underTest.state.onEach { latest = it }.launchIn(this)
+        val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
 
-        assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden)
+        assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
 
         job.cancel()
     }
@@ -100,11 +101,11 @@
     @Test
     fun `affordance - query not successful - model is none`() = runBlockingTest {
         setUpState(isWalletQuerySuccessful = false)
-        var latest: KeyguardQuickAffordanceConfig.State? = null
+        var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
 
-        val job = underTest.state.onEach { latest = it }.launchIn(this)
+        val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
 
-        assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden)
+        assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
 
         job.cancel()
     }
@@ -112,11 +113,11 @@
     @Test
     fun `affordance - missing icon - model is none`() = runBlockingTest {
         setUpState(hasWalletIcon = false)
-        var latest: KeyguardQuickAffordanceConfig.State? = null
+        var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
 
-        val job = underTest.state.onEach { latest = it }.launchIn(this)
+        val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
 
-        assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden)
+        assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
 
         job.cancel()
     }
@@ -124,24 +125,24 @@
     @Test
     fun `affordance - no selected card - model is none`() = runBlockingTest {
         setUpState(hasWalletIcon = false)
-        var latest: KeyguardQuickAffordanceConfig.State? = null
+        var latest: KeyguardQuickAffordanceConfig.LockScreenState? = null
 
-        val job = underTest.state.onEach { latest = it }.launchIn(this)
+        val job = underTest.lockScreenState.onEach { latest = it }.launchIn(this)
 
-        assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.State.Hidden)
+        assertThat(latest).isEqualTo(KeyguardQuickAffordanceConfig.LockScreenState.Hidden)
 
         job.cancel()
     }
 
     @Test
-    fun onQuickAffordanceClicked() {
+    fun onQuickAffordanceTriggered() {
         val animationController: ActivityLaunchAnimator.Controller = mock()
         val expandable: Expandable = mock {
             whenever(this.activityLaunchController()).thenReturn(animationController)
         }
 
-        assertThat(underTest.onQuickAffordanceClicked(expandable))
-            .isEqualTo(KeyguardQuickAffordanceConfig.OnClickedResult.Handled)
+        assertThat(underTest.onTriggered(expandable))
+            .isEqualTo(KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled)
         verify(walletController)
             .startQuickAccessUiIntent(
                 activityStarter,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
new file mode 100644
index 0000000..5a7f2bb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
@@ -0,0 +1,152 @@
+/*
+ * Copyright (C) 2022 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.keyguard.data.repository
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
+import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation
+import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardQuickAffordanceRepositoryTest : SysuiTestCase() {
+
+    private lateinit var underTest: KeyguardQuickAffordanceRepository
+
+    private lateinit var config1: FakeKeyguardQuickAffordanceConfig
+    private lateinit var config2: FakeKeyguardQuickAffordanceConfig
+
+    @Before
+    fun setUp() {
+        config1 = FakeKeyguardQuickAffordanceConfig("built_in:1")
+        config2 = FakeKeyguardQuickAffordanceConfig("built_in:2")
+        underTest =
+            KeyguardQuickAffordanceRepository(
+                scope = CoroutineScope(IMMEDIATE),
+                backgroundDispatcher = IMMEDIATE,
+                selectionManager = KeyguardQuickAffordanceSelectionManager(),
+                configs = setOf(config1, config2),
+            )
+    }
+
+    @Test
+    fun setSelections() =
+        runBlocking(IMMEDIATE) {
+            var configsBySlotId: Map<String, List<KeyguardQuickAffordanceConfig>>? = null
+            val job = underTest.selections.onEach { configsBySlotId = it }.launchIn(this)
+            val slotId1 = "slot1"
+            val slotId2 = "slot2"
+
+            underTest.setSelections(slotId1, listOf(config1.key))
+            assertSelections(
+                configsBySlotId,
+                mapOf(
+                    slotId1 to listOf(config1),
+                ),
+            )
+
+            underTest.setSelections(slotId2, listOf(config2.key))
+            assertSelections(
+                configsBySlotId,
+                mapOf(
+                    slotId1 to listOf(config1),
+                    slotId2 to listOf(config2),
+                ),
+            )
+
+            underTest.setSelections(slotId1, emptyList())
+            underTest.setSelections(slotId2, listOf(config1.key))
+            assertSelections(
+                configsBySlotId,
+                mapOf(
+                    slotId1 to emptyList(),
+                    slotId2 to listOf(config1),
+                ),
+            )
+
+            job.cancel()
+        }
+
+    @Test
+    fun getAffordancePickerRepresentations() {
+        assertThat(underTest.getAffordancePickerRepresentations())
+            .isEqualTo(
+                listOf(
+                    KeyguardQuickAffordancePickerRepresentation(
+                        id = config1.key,
+                        name = config1.pickerName,
+                        iconResourceId = config1.pickerIconResourceId,
+                    ),
+                    KeyguardQuickAffordancePickerRepresentation(
+                        id = config2.key,
+                        name = config2.pickerName,
+                        iconResourceId = config2.pickerIconResourceId,
+                    ),
+                )
+            )
+    }
+
+    @Test
+    fun getSlotPickerRepresentations() {
+        assertThat(underTest.getSlotPickerRepresentations())
+            .isEqualTo(
+                listOf(
+                    KeyguardSlotPickerRepresentation(
+                        id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+                        maxSelectedAffordances = 1,
+                    ),
+                    KeyguardSlotPickerRepresentation(
+                        id = KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+                        maxSelectedAffordances = 1,
+                    ),
+                )
+            )
+    }
+
+    private suspend fun assertSelections(
+        observed: Map<String, List<KeyguardQuickAffordanceConfig>>?,
+        expected: Map<String, List<KeyguardQuickAffordanceConfig>>,
+    ) {
+        assertThat(observed).isEqualTo(expected)
+        assertThat(underTest.getSelections())
+            .isEqualTo(expected.mapValues { (_, configs) -> configs.map { it.key } })
+        expected.forEach { (slotId, configs) ->
+            assertThat(underTest.getSelections(slotId)).isEqualTo(configs)
+        }
+    }
+
+    companion object {
+        private val IMMEDIATE = Dispatchers.Main.immediate
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
index 7a15680..53d9b87 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardRepositoryImplTest.kt
@@ -20,7 +20,11 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.shared.model.Position
 import com.android.systemui.doze.DozeHost
+import com.android.systemui.keyguard.WakefulnessLifecycle
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
+import com.android.systemui.keyguard.shared.model.WakefulnessModel
 import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.statusbar.phone.BiometricUnlockController
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.whenever
@@ -43,6 +47,8 @@
     @Mock private lateinit var statusBarStateController: StatusBarStateController
     @Mock private lateinit var dozeHost: DozeHost
     @Mock private lateinit var keyguardStateController: KeyguardStateController
+    @Mock private lateinit var wakefulnessLifecycle: WakefulnessLifecycle
+    @Mock private lateinit var biometricUnlockController: BiometricUnlockController
 
     private lateinit var underTest: KeyguardRepositoryImpl
 
@@ -55,6 +61,8 @@
                 statusBarStateController,
                 keyguardStateController,
                 dozeHost,
+                wakefulnessLifecycle,
+                biometricUnlockController,
             )
     }
 
@@ -184,4 +192,92 @@
         job.cancel()
         verify(statusBarStateController).removeCallback(captor.value)
     }
+
+    @Test
+    fun wakefulness() = runBlockingTest {
+        val values = mutableListOf<WakefulnessModel>()
+        val job = underTest.wakefulnessState.onEach(values::add).launchIn(this)
+
+        val captor = argumentCaptor<WakefulnessLifecycle.Observer>()
+        verify(wakefulnessLifecycle).addObserver(captor.capture())
+
+        captor.value.onStartedWakingUp()
+        captor.value.onFinishedWakingUp()
+        captor.value.onStartedGoingToSleep()
+        captor.value.onFinishedGoingToSleep()
+
+        assertThat(values)
+            .isEqualTo(
+                listOf(
+                    // Initial value will be ASLEEP
+                    WakefulnessModel.ASLEEP,
+                    WakefulnessModel.STARTING_TO_WAKE,
+                    WakefulnessModel.AWAKE,
+                    WakefulnessModel.STARTING_TO_SLEEP,
+                    WakefulnessModel.ASLEEP,
+                )
+            )
+
+        job.cancel()
+        verify(wakefulnessLifecycle).removeObserver(captor.value)
+    }
+
+    @Test
+    fun isBouncerShowing() = runBlockingTest {
+        whenever(keyguardStateController.isBouncerShowing).thenReturn(false)
+        var latest: Boolean? = null
+        val job = underTest.isBouncerShowing.onEach { latest = it }.launchIn(this)
+
+        assertThat(latest).isFalse()
+
+        val captor = argumentCaptor<KeyguardStateController.Callback>()
+        verify(keyguardStateController).addCallback(captor.capture())
+
+        whenever(keyguardStateController.isBouncerShowing).thenReturn(true)
+        captor.value.onBouncerShowingChanged()
+        assertThat(latest).isTrue()
+
+        whenever(keyguardStateController.isBouncerShowing).thenReturn(false)
+        captor.value.onBouncerShowingChanged()
+        assertThat(latest).isFalse()
+
+        job.cancel()
+    }
+
+    @Test
+    fun biometricUnlockState() = runBlockingTest {
+        val values = mutableListOf<BiometricUnlockModel>()
+        val job = underTest.biometricUnlockState.onEach(values::add).launchIn(this)
+
+        val captor = argumentCaptor<BiometricUnlockController.BiometricModeListener>()
+        verify(biometricUnlockController).addBiometricModeListener(captor.capture())
+
+        captor.value.onModeChanged(BiometricUnlockController.MODE_NONE)
+        captor.value.onModeChanged(BiometricUnlockController.MODE_WAKE_AND_UNLOCK)
+        captor.value.onModeChanged(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING)
+        captor.value.onModeChanged(BiometricUnlockController.MODE_SHOW_BOUNCER)
+        captor.value.onModeChanged(BiometricUnlockController.MODE_ONLY_WAKE)
+        captor.value.onModeChanged(BiometricUnlockController.MODE_UNLOCK_COLLAPSING)
+        captor.value.onModeChanged(BiometricUnlockController.MODE_DISMISS_BOUNCER)
+        captor.value.onModeChanged(BiometricUnlockController.MODE_WAKE_AND_UNLOCK_FROM_DREAM)
+
+        assertThat(values)
+            .isEqualTo(
+                listOf(
+                    // Initial value will be NONE, followed by onModeChanged() call
+                    BiometricUnlockModel.NONE,
+                    BiometricUnlockModel.NONE,
+                    BiometricUnlockModel.WAKE_AND_UNLOCK,
+                    BiometricUnlockModel.WAKE_AND_UNLOCK_PULSING,
+                    BiometricUnlockModel.SHOW_BOUNCER,
+                    BiometricUnlockModel.ONLY_WAKE,
+                    BiometricUnlockModel.UNLOCK_COLLAPSING,
+                    BiometricUnlockModel.DISMISS_BOUNCER,
+                    BiometricUnlockModel.WAKE_AND_UNLOCK_FROM_DREAM,
+                )
+            )
+
+        job.cancel()
+        verify(biometricUnlockController).removeBiometricModeListener(captor.value)
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
index 1b34100..27d5d0a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/KeyguardTransitionRepositoryTest.kt
@@ -63,7 +63,7 @@
 
     @Before
     fun setUp() {
-        underTest = KeyguardTransitionRepository()
+        underTest = KeyguardTransitionRepositoryImpl()
         wtfHandler = WtfHandler()
         oldWtfHandler = Log.setWtfHandler(wtfHandler)
     }
@@ -128,11 +128,15 @@
 
             assertThat(steps.size).isEqualTo(3)
             assertThat(steps[0])
-                .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED))
+                .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED, OWNER_NAME))
             assertThat(steps[1])
-                .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0.5f, TransitionState.RUNNING))
+                .isEqualTo(
+                    TransitionStep(AOD, LOCKSCREEN, 0.5f, TransitionState.RUNNING, OWNER_NAME)
+                )
             assertThat(steps[2])
-                .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 1f, TransitionState.FINISHED))
+                .isEqualTo(
+                    TransitionStep(AOD, LOCKSCREEN, 1f, TransitionState.FINISHED, OWNER_NAME)
+                )
             job.cancel()
         }
 
@@ -174,18 +178,22 @@
     }
 
     private fun assertSteps(steps: List<TransitionStep>, fractions: List<BigDecimal>) {
-        // + 2 accounts for start and finish of automated transition
-        assertThat(steps.size).isEqualTo(fractions.size + 2)
-
-        assertThat(steps[0]).isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED))
+        assertThat(steps[0])
+            .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 0f, TransitionState.STARTED, OWNER_NAME))
         fractions.forEachIndexed { index, fraction ->
             assertThat(steps[index + 1])
                 .isEqualTo(
-                    TransitionStep(AOD, LOCKSCREEN, fraction.toFloat(), TransitionState.RUNNING)
+                    TransitionStep(
+                        AOD,
+                        LOCKSCREEN,
+                        fraction.toFloat(),
+                        TransitionState.RUNNING,
+                        OWNER_NAME
+                    )
                 )
         }
         assertThat(steps[steps.size - 1])
-            .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 1f, TransitionState.FINISHED))
+            .isEqualTo(TransitionStep(AOD, LOCKSCREEN, 1f, TransitionState.FINISHED, OWNER_NAME))
 
         assertThat(wtfHandler.failed).isFalse()
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractorTest.kt
index e6c8dd8..5743b2f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/BouncerInteractorTest.kt
@@ -27,8 +27,8 @@
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.data.BouncerView
+import com.android.systemui.keyguard.data.BouncerViewDelegate
 import com.android.systemui.keyguard.data.repository.KeyguardBouncerRepository
-import com.android.systemui.keyguard.shared.model.BouncerCallbackActionsModel
 import com.android.systemui.keyguard.shared.model.BouncerShowMessageModel
 import com.android.systemui.keyguard.shared.model.KeyguardBouncerModel
 import com.android.systemui.plugins.ActivityStarter
@@ -57,6 +57,7 @@
     @Mock(answer = Answers.RETURNS_DEEP_STUBS)
     private lateinit var repository: KeyguardBouncerRepository
     @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var bouncerView: BouncerView
+    @Mock private lateinit var bouncerViewDelegate: BouncerViewDelegate
     @Mock private lateinit var keyguardStateController: KeyguardStateController
     @Mock private lateinit var keyguardSecurityModel: KeyguardSecurityModel
     @Mock private lateinit var bouncerCallbackInteractor: BouncerCallbackInteractor
@@ -86,6 +87,7 @@
             )
         `when`(repository.startingDisappearAnimation.value).thenReturn(null)
         `when`(repository.show.value).thenReturn(null)
+        `when`(bouncerView.delegate).thenReturn(bouncerViewDelegate)
     }
 
     @Test
@@ -97,7 +99,7 @@
         verify(repository).setHide(false)
         verify(repository).setStartingToHide(false)
         verify(repository).setScrimmed(true)
-        verify(repository).setExpansion(EXPANSION_VISIBLE)
+        verify(repository).setPanelExpansion(EXPANSION_VISIBLE)
         verify(repository).setShowingSoon(true)
         verify(keyguardStateController).notifyBouncerShowing(true)
         verify(bouncerCallbackInteractor).dispatchStartingToShow()
@@ -108,7 +110,7 @@
 
     @Test
     fun testShow_isNotScrimmed() {
-        verify(repository, never()).setExpansion(EXPANSION_VISIBLE)
+        verify(repository, never()).setPanelExpansion(EXPANSION_VISIBLE)
     }
 
     @Test
@@ -124,7 +126,6 @@
         verify(falsingCollector).onBouncerHidden()
         verify(keyguardStateController).notifyBouncerShowing(false)
         verify(repository).setShowingSoon(false)
-        verify(repository).setOnDismissAction(null)
         verify(repository).setVisible(false)
         verify(repository).setHide(true)
         verify(repository).setShow(null)
@@ -132,26 +133,26 @@
 
     @Test
     fun testExpansion() {
-        `when`(repository.expansionAmount.value).thenReturn(0.5f)
-        bouncerInteractor.setExpansion(0.6f)
-        verify(repository).setExpansion(0.6f)
+        `when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
+        bouncerInteractor.setPanelExpansion(0.6f)
+        verify(repository).setPanelExpansion(0.6f)
         verify(bouncerCallbackInteractor).dispatchExpansionChanged(0.6f)
     }
 
     @Test
     fun testExpansion_fullyShown() {
-        `when`(repository.expansionAmount.value).thenReturn(0.5f)
+        `when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
         `when`(repository.startingDisappearAnimation.value).thenReturn(null)
-        bouncerInteractor.setExpansion(EXPANSION_VISIBLE)
+        bouncerInteractor.setPanelExpansion(EXPANSION_VISIBLE)
         verify(falsingCollector).onBouncerShown()
         verify(bouncerCallbackInteractor).dispatchFullyShown()
     }
 
     @Test
     fun testExpansion_fullyHidden() {
-        `when`(repository.expansionAmount.value).thenReturn(0.5f)
+        `when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
         `when`(repository.startingDisappearAnimation.value).thenReturn(null)
-        bouncerInteractor.setExpansion(EXPANSION_HIDDEN)
+        bouncerInteractor.setPanelExpansion(EXPANSION_HIDDEN)
         verify(repository).setVisible(false)
         verify(repository).setShow(null)
         verify(falsingCollector).onBouncerHidden()
@@ -161,8 +162,8 @@
 
     @Test
     fun testExpansion_startingToHide() {
-        `when`(repository.expansionAmount.value).thenReturn(EXPANSION_VISIBLE)
-        bouncerInteractor.setExpansion(0.1f)
+        `when`(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE)
+        bouncerInteractor.setPanelExpansion(0.1f)
         verify(repository).setStartingToHide(true)
         verify(bouncerCallbackInteractor).dispatchStartingToHide()
     }
@@ -178,8 +179,7 @@
         val onDismissAction = mock(ActivityStarter.OnDismissAction::class.java)
         val cancelAction = mock(Runnable::class.java)
         bouncerInteractor.setDismissAction(onDismissAction, cancelAction)
-        verify(repository)
-            .setOnDismissAction(BouncerCallbackActionsModel(onDismissAction, cancelAction))
+        verify(bouncerViewDelegate).setDismissAction(onDismissAction, cancelAction)
     }
 
     @Test
@@ -234,7 +234,7 @@
     @Test
     fun testIsFullShowing() {
         `when`(repository.isVisible.value).thenReturn(true)
-        `when`(repository.expansionAmount.value).thenReturn(EXPANSION_VISIBLE)
+        `when`(repository.panelExpansionAmount.value).thenReturn(EXPANSION_VISIBLE)
         `when`(repository.startingDisappearAnimation.value).thenReturn(null)
         assertThat(bouncerInteractor.isFullyShowing()).isTrue()
         `when`(repository.isVisible.value).thenReturn(false)
@@ -255,7 +255,7 @@
         assertThat(bouncerInteractor.isInTransit()).isTrue()
         `when`(repository.showingSoon.value).thenReturn(false)
         assertThat(bouncerInteractor.isInTransit()).isFalse()
-        `when`(repository.expansionAmount.value).thenReturn(0.5f)
+        `when`(repository.panelExpansionAmount.value).thenReturn(0.5f)
         assertThat(bouncerInteractor.isInTransit()).isTrue()
     }
 
@@ -269,10 +269,9 @@
 
     @Test
     fun testWillDismissWithAction() {
-        `when`(repository.onDismissAction.value?.onDismissAction)
-            .thenReturn(mock(ActivityStarter.OnDismissAction::class.java))
+        `when`(bouncerViewDelegate.willDismissWithActions()).thenReturn(true)
         assertThat(bouncerInteractor.willDismissWithAction()).isTrue()
-        `when`(repository.onDismissAction.value?.onDismissAction).thenReturn(null)
+        `when`(bouncerViewDelegate.willDismissWithActions()).thenReturn(false)
         assertThat(bouncerInteractor.willDismissWithAction()).isFalse()
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
index 114cf19..8b6603d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorParameterizedTest.kt
@@ -25,10 +25,14 @@
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
 import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
 import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
 import com.android.systemui.plugins.ActivityStarter
@@ -37,6 +41,8 @@
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.test.runBlockingTest
 import org.junit.Before
 import org.junit.Test
@@ -189,6 +195,8 @@
                     /* startActivity= */ true,
                 ),
             )
+
+        private val IMMEDIATE = Dispatchers.Main.immediate
     }
 
     @Mock private lateinit var lockPatternUtils: LockPatternUtils
@@ -213,10 +221,20 @@
         whenever(expandable.activityLaunchController()).thenReturn(animationController)
 
         homeControls =
-            object :
-                FakeKeyguardQuickAffordanceConfig(
-                    BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
-                ) {}
+            FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS)
+        val quickAccessWallet =
+            FakeKeyguardQuickAffordanceConfig(
+                BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
+            )
+        val qrCodeScanner =
+            FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER)
+        val quickAffordanceRepository =
+            KeyguardQuickAffordanceRepository(
+                scope = CoroutineScope(IMMEDIATE),
+                backgroundDispatcher = IMMEDIATE,
+                selectionManager = KeyguardQuickAffordanceSelectionManager(),
+                configs = setOf(homeControls, quickAccessWallet, qrCodeScanner),
+            )
         underTest =
             KeyguardQuickAffordanceInteractor(
                 keyguardInteractor = KeyguardInteractor(repository = FakeKeyguardRepository()),
@@ -229,14 +247,8 @@
                                 ),
                             KeyguardQuickAffordancePosition.BOTTOM_END to
                                 listOf(
-                                    object :
-                                        FakeKeyguardQuickAffordanceConfig(
-                                            BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
-                                        ) {},
-                                    object :
-                                        FakeKeyguardQuickAffordanceConfig(
-                                            BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER
-                                        ) {},
+                                    quickAccessWallet,
+                                    qrCodeScanner,
                                 ),
                         ),
                     ),
@@ -244,33 +256,38 @@
                 keyguardStateController = keyguardStateController,
                 userTracker = userTracker,
                 activityStarter = activityStarter,
+                featureFlags =
+                    FakeFeatureFlags().apply {
+                        set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false)
+                    },
+                repository = { quickAffordanceRepository },
             )
     }
 
     @Test
-    fun onQuickAffordanceClicked() = runBlockingTest {
+    fun onQuickAffordanceTriggered() = runBlockingTest {
         setUpMocks(
             needStrongAuthAfterBoot = needStrongAuthAfterBoot,
             keyguardIsUnlocked = keyguardIsUnlocked,
         )
 
         homeControls.setState(
-            state =
-                KeyguardQuickAffordanceConfig.State.Visible(
+            lockScreenState =
+                KeyguardQuickAffordanceConfig.LockScreenState.Visible(
                     icon = DRAWABLE,
                 )
         )
-        homeControls.onClickedResult =
+        homeControls.onTriggeredResult =
             if (startActivity) {
-                KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity(
+                KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity(
                     intent = INTENT,
                     canShowWhileLocked = canShowWhileLocked,
                 )
             } else {
-                KeyguardQuickAffordanceConfig.OnClickedResult.Handled
+                KeyguardQuickAffordanceConfig.OnTriggeredResult.Handled
             }
 
-        underTest.onQuickAffordanceClicked(
+        underTest.onQuickAffordanceTriggered(
             configKey = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS,
             expandable = expandable,
         )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index 1a1ee8a..3364535 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -22,22 +22,31 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
 import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
 import com.android.systemui.keyguard.domain.model.KeyguardQuickAffordanceModel
 import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
+import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
-import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordanceToggleState
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.settings.UserTracker
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.test.runBlockingTest
 import kotlinx.coroutines.yield
 import org.junit.Before
@@ -47,6 +56,7 @@
 import org.mockito.Mock
 import org.mockito.MockitoAnnotations
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(JUnit4::class)
 class KeyguardQuickAffordanceInteractorTest : SysuiTestCase() {
@@ -62,6 +72,7 @@
     private lateinit var homeControls: FakeKeyguardQuickAffordanceConfig
     private lateinit var quickAccessWallet: FakeKeyguardQuickAffordanceConfig
     private lateinit var qrCodeScanner: FakeKeyguardQuickAffordanceConfig
+    private lateinit var featureFlags: FakeFeatureFlags
 
     @Before
     fun setUp() {
@@ -71,20 +82,25 @@
         repository.setKeyguardShowing(true)
 
         homeControls =
-            object :
-                FakeKeyguardQuickAffordanceConfig(
-                    BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
-                ) {}
+            FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS)
         quickAccessWallet =
-            object :
-                FakeKeyguardQuickAffordanceConfig(
-                    BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
-                ) {}
+            FakeKeyguardQuickAffordanceConfig(
+                BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
+            )
         qrCodeScanner =
-            object :
-                FakeKeyguardQuickAffordanceConfig(
-                    BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER
-                ) {}
+            FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER)
+
+        val quickAffordanceRepository =
+            KeyguardQuickAffordanceRepository(
+                scope = CoroutineScope(IMMEDIATE),
+                backgroundDispatcher = IMMEDIATE,
+                selectionManager = KeyguardQuickAffordanceSelectionManager(),
+                configs = setOf(homeControls, quickAccessWallet, qrCodeScanner),
+            )
+        featureFlags =
+            FakeFeatureFlags().apply {
+                set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false)
+            }
 
         underTest =
             KeyguardQuickAffordanceInteractor(
@@ -107,6 +123,8 @@
                 keyguardStateController = keyguardStateController,
                 userTracker = userTracker,
                 activityStarter = activityStarter,
+                featureFlags = featureFlags,
+                repository = { quickAffordanceRepository },
             )
     }
 
@@ -114,9 +132,9 @@
     fun `quickAffordance - bottom start affordance is visible`() = runBlockingTest {
         val configKey = BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
         homeControls.setState(
-            KeyguardQuickAffordanceConfig.State.Visible(
+            KeyguardQuickAffordanceConfig.LockScreenState.Visible(
                 icon = ICON,
-                toggle = KeyguardQuickAffordanceToggleState.On,
+                activationState = ActivationState.Active,
             )
         )
 
@@ -137,7 +155,7 @@
         assertThat(visibleModel.icon).isEqualTo(ICON)
         assertThat(visibleModel.icon.contentDescription)
             .isEqualTo(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID))
-        assertThat(visibleModel.toggle).isEqualTo(KeyguardQuickAffordanceToggleState.On)
+        assertThat(visibleModel.activationState).isEqualTo(ActivationState.Active)
         job.cancel()
     }
 
@@ -145,7 +163,7 @@
     fun `quickAffordance - bottom end affordance is visible`() = runBlockingTest {
         val configKey = BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
         quickAccessWallet.setState(
-            KeyguardQuickAffordanceConfig.State.Visible(
+            KeyguardQuickAffordanceConfig.LockScreenState.Visible(
                 icon = ICON,
             )
         )
@@ -167,7 +185,7 @@
         assertThat(visibleModel.icon).isEqualTo(ICON)
         assertThat(visibleModel.icon.contentDescription)
             .isEqualTo(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID))
-        assertThat(visibleModel.toggle).isEqualTo(KeyguardQuickAffordanceToggleState.NotSupported)
+        assertThat(visibleModel.activationState).isEqualTo(ActivationState.NotSupported)
         job.cancel()
     }
 
@@ -175,7 +193,7 @@
     fun `quickAffordance - bottom start affordance hidden while dozing`() = runBlockingTest {
         repository.setDozing(true)
         homeControls.setState(
-            KeyguardQuickAffordanceConfig.State.Visible(
+            KeyguardQuickAffordanceConfig.LockScreenState.Visible(
                 icon = ICON,
             )
         )
@@ -195,7 +213,7 @@
         runBlockingTest {
             repository.setKeyguardShowing(false)
             homeControls.setState(
-                KeyguardQuickAffordanceConfig.State.Visible(
+                KeyguardQuickAffordanceConfig.LockScreenState.Visible(
                     icon = ICON,
                 )
             )
@@ -210,6 +228,270 @@
             job.cancel()
         }
 
+    @Test
+    fun select() =
+        runBlocking(IMMEDIATE) {
+            featureFlags.set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
+            homeControls.setState(
+                KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
+            )
+            quickAccessWallet.setState(
+                KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
+            )
+            qrCodeScanner.setState(
+                KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
+            )
+
+            assertThat(underTest.getSelections())
+                .isEqualTo(
+                    mapOf<String, List<String>>(
+                        KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to emptyList(),
+                        KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to emptyList(),
+                    )
+                )
+
+            var startConfig: KeyguardQuickAffordanceModel? = null
+            val job1 =
+                underTest
+                    .quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
+                    .onEach { startConfig = it }
+                    .launchIn(this)
+            var endConfig: KeyguardQuickAffordanceModel? = null
+            val job2 =
+                underTest
+                    .quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_END)
+                    .onEach { endConfig = it }
+                    .launchIn(this)
+
+            underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, homeControls.key)
+            yield()
+            yield()
+            assertThat(startConfig)
+                .isEqualTo(
+                    KeyguardQuickAffordanceModel.Visible(
+                        configKey =
+                            KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START +
+                                "::${homeControls.key}",
+                        icon = ICON,
+                        activationState = ActivationState.NotSupported,
+                    )
+                )
+            assertThat(endConfig)
+                .isEqualTo(
+                    KeyguardQuickAffordanceModel.Hidden,
+                )
+            assertThat(underTest.getSelections())
+                .isEqualTo(
+                    mapOf(
+                        KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to
+                            listOf(homeControls.key),
+                        KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to emptyList(),
+                    )
+                )
+
+            underTest.select(
+                KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+                quickAccessWallet.key
+            )
+            yield()
+            yield()
+            assertThat(startConfig)
+                .isEqualTo(
+                    KeyguardQuickAffordanceModel.Visible(
+                        configKey =
+                            KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START +
+                                "::${quickAccessWallet.key}",
+                        icon = ICON,
+                        activationState = ActivationState.NotSupported,
+                    )
+                )
+            assertThat(endConfig)
+                .isEqualTo(
+                    KeyguardQuickAffordanceModel.Hidden,
+                )
+            assertThat(underTest.getSelections())
+                .isEqualTo(
+                    mapOf(
+                        KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to
+                            listOf(quickAccessWallet.key),
+                        KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to emptyList(),
+                    )
+                )
+
+            underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, qrCodeScanner.key)
+            yield()
+            yield()
+            assertThat(startConfig)
+                .isEqualTo(
+                    KeyguardQuickAffordanceModel.Visible(
+                        configKey =
+                            KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START +
+                                "::${quickAccessWallet.key}",
+                        icon = ICON,
+                        activationState = ActivationState.NotSupported,
+                    )
+                )
+            assertThat(endConfig)
+                .isEqualTo(
+                    KeyguardQuickAffordanceModel.Visible(
+                        configKey =
+                            KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END +
+                                "::${qrCodeScanner.key}",
+                        icon = ICON,
+                        activationState = ActivationState.NotSupported,
+                    )
+                )
+            assertThat(underTest.getSelections())
+                .isEqualTo(
+                    mapOf(
+                        KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to
+                            listOf(quickAccessWallet.key),
+                        KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to
+                            listOf(qrCodeScanner.key),
+                    )
+                )
+
+            job1.cancel()
+            job2.cancel()
+        }
+
+    @Test
+    fun `unselect - one`() =
+        runBlocking(IMMEDIATE) {
+            featureFlags.set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
+            homeControls.setState(
+                KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
+            )
+            quickAccessWallet.setState(
+                KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
+            )
+            qrCodeScanner.setState(
+                KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
+            )
+
+            var startConfig: KeyguardQuickAffordanceModel? = null
+            val job1 =
+                underTest
+                    .quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_START)
+                    .onEach { startConfig = it }
+                    .launchIn(this)
+            var endConfig: KeyguardQuickAffordanceModel? = null
+            val job2 =
+                underTest
+                    .quickAffordance(KeyguardQuickAffordancePosition.BOTTOM_END)
+                    .onEach { endConfig = it }
+                    .launchIn(this)
+            underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, homeControls.key)
+            yield()
+            yield()
+            underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, quickAccessWallet.key)
+            yield()
+            yield()
+
+            underTest.unselect(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, homeControls.key)
+            yield()
+            yield()
+
+            assertThat(startConfig)
+                .isEqualTo(
+                    KeyguardQuickAffordanceModel.Hidden,
+                )
+            assertThat(endConfig)
+                .isEqualTo(
+                    KeyguardQuickAffordanceModel.Visible(
+                        configKey =
+                            KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END +
+                                "::${quickAccessWallet.key}",
+                        icon = ICON,
+                        activationState = ActivationState.NotSupported,
+                    )
+                )
+            assertThat(underTest.getSelections())
+                .isEqualTo(
+                    mapOf(
+                        KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to emptyList(),
+                        KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to
+                            listOf(quickAccessWallet.key),
+                    )
+                )
+
+            underTest.unselect(
+                KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+                quickAccessWallet.key
+            )
+            yield()
+            yield()
+
+            assertThat(startConfig)
+                .isEqualTo(
+                    KeyguardQuickAffordanceModel.Hidden,
+                )
+            assertThat(endConfig)
+                .isEqualTo(
+                    KeyguardQuickAffordanceModel.Hidden,
+                )
+            assertThat(underTest.getSelections())
+                .isEqualTo(
+                    mapOf<String, List<String>>(
+                        KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to emptyList(),
+                        KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to emptyList(),
+                    )
+                )
+
+            job1.cancel()
+            job2.cancel()
+        }
+
+    @Test
+    fun `unselect - all`() =
+        runBlocking(IMMEDIATE) {
+            featureFlags.set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, true)
+            homeControls.setState(
+                KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
+            )
+            quickAccessWallet.setState(
+                KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
+            )
+            qrCodeScanner.setState(
+                KeyguardQuickAffordanceConfig.LockScreenState.Visible(icon = ICON)
+            )
+
+            underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, homeControls.key)
+            yield()
+            yield()
+            underTest.select(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END, quickAccessWallet.key)
+            yield()
+            yield()
+
+            underTest.unselect(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, null)
+            yield()
+            yield()
+
+            assertThat(underTest.getSelections())
+                .isEqualTo(
+                    mapOf(
+                        KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to emptyList(),
+                        KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to
+                            listOf(quickAccessWallet.key),
+                    )
+                )
+
+            underTest.unselect(
+                KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END,
+                null,
+            )
+            yield()
+            yield()
+
+            assertThat(underTest.getSelections())
+                .isEqualTo(
+                    mapOf<String, List<String>>(
+                        KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START to emptyList(),
+                        KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END to emptyList(),
+                    )
+                )
+        }
+
     companion object {
         private val ICON: Icon = mock {
             whenever(this.contentDescription)
@@ -220,5 +502,6 @@
                 )
         }
         private const val CONTENT_DESCRIPTION_RESOURCE_ID = 1337
+        private val IMMEDIATE = Dispatchers.Main.immediate
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
new file mode 100644
index 0000000..6333b24
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionInteractorTest.kt
@@ -0,0 +1,199 @@
+/*
+ * Copyright (C) 2022 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.keyguard.domain.interactor
+
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+import com.android.systemui.keyguard.shared.model.TransitionState.FINISHED
+import com.android.systemui.keyguard.shared.model.TransitionState.RUNNING
+import com.android.systemui.keyguard.shared.model.TransitionState.STARTED
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@SmallTest
+@RunWith(JUnit4::class)
+class KeyguardTransitionInteractorTest : SysuiTestCase() {
+
+    private lateinit var underTest: KeyguardTransitionInteractor
+    private lateinit var repository: FakeKeyguardTransitionRepository
+
+    @Before
+    fun setUp() {
+        repository = FakeKeyguardTransitionRepository()
+        underTest = KeyguardTransitionInteractor(repository)
+    }
+
+    @Test
+    fun `transition collectors receives only appropriate events`() =
+        runBlocking(IMMEDIATE) {
+            var lockscreenToAodSteps = mutableListOf<TransitionStep>()
+            val job1 =
+                underTest.lockscreenToAodTransition
+                    .onEach { lockscreenToAodSteps.add(it) }
+                    .launchIn(this)
+
+            var aodToLockscreenSteps = mutableListOf<TransitionStep>()
+            val job2 =
+                underTest.aodToLockscreenTransition
+                    .onEach { aodToLockscreenSteps.add(it) }
+                    .launchIn(this)
+
+            val steps = mutableListOf<TransitionStep>()
+            steps.add(TransitionStep(AOD, GONE, 0f, STARTED))
+            steps.add(TransitionStep(AOD, GONE, 1f, FINISHED))
+            steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED))
+            steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING))
+            steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED))
+            steps.add(TransitionStep(LOCKSCREEN, AOD, 0f, STARTED))
+            steps.add(TransitionStep(LOCKSCREEN, AOD, 0.1f, RUNNING))
+            steps.add(TransitionStep(LOCKSCREEN, AOD, 0.2f, RUNNING))
+
+            steps.forEach { repository.sendTransitionStep(it) }
+
+            assertThat(aodToLockscreenSteps).isEqualTo(steps.subList(2, 5))
+            assertThat(lockscreenToAodSteps).isEqualTo(steps.subList(5, 8))
+
+            job1.cancel()
+            job2.cancel()
+        }
+
+    @Test
+    fun dozeAmountTransitionTest() =
+        runBlocking(IMMEDIATE) {
+            var dozeAmountSteps = mutableListOf<TransitionStep>()
+            val job =
+                underTest.dozeAmountTransition.onEach { dozeAmountSteps.add(it) }.launchIn(this)
+
+            val steps = mutableListOf<TransitionStep>()
+
+            steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED))
+            steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING))
+            steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED))
+            steps.add(TransitionStep(LOCKSCREEN, AOD, 0f, STARTED))
+            steps.add(TransitionStep(LOCKSCREEN, AOD, 0.8f, RUNNING))
+            steps.add(TransitionStep(LOCKSCREEN, AOD, 0.9f, RUNNING))
+            steps.add(TransitionStep(LOCKSCREEN, AOD, 1f, FINISHED))
+
+            steps.forEach { repository.sendTransitionStep(it) }
+
+            assertThat(dozeAmountSteps.subList(0, 3))
+                .isEqualTo(
+                    listOf(
+                        steps[0].copy(value = 1f - steps[0].value),
+                        steps[1].copy(value = 1f - steps[1].value),
+                        steps[2].copy(value = 1f - steps[2].value),
+                    )
+                )
+            assertThat(dozeAmountSteps.subList(3, 7)).isEqualTo(steps.subList(3, 7))
+
+            job.cancel()
+        }
+
+    @Test
+    fun keyguardStateTests() =
+        runBlocking(IMMEDIATE) {
+            var finishedSteps = mutableListOf<KeyguardState>()
+            val job =
+                underTest.finishedKeyguardState.onEach { finishedSteps.add(it) }.launchIn(this)
+
+            val steps = mutableListOf<TransitionStep>()
+
+            steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED))
+            steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING))
+            steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED))
+            steps.add(TransitionStep(LOCKSCREEN, AOD, 0f, STARTED))
+            steps.add(TransitionStep(LOCKSCREEN, AOD, 0.9f, RUNNING))
+            steps.add(TransitionStep(LOCKSCREEN, AOD, 1f, FINISHED))
+            steps.add(TransitionStep(AOD, GONE, 1f, STARTED))
+
+            steps.forEach { repository.sendTransitionStep(it) }
+
+            assertThat(finishedSteps).isEqualTo(listOf(LOCKSCREEN, AOD))
+
+            job.cancel()
+        }
+
+    @Test
+    fun finishedKeyguardTransitionStepTests() =
+        runBlocking(IMMEDIATE) {
+            var finishedSteps = mutableListOf<TransitionStep>()
+            val job =
+                underTest.finishedKeyguardTransitionStep
+                    .onEach { finishedSteps.add(it) }
+                    .launchIn(this)
+
+            val steps = mutableListOf<TransitionStep>()
+
+            steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED))
+            steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING))
+            steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED))
+            steps.add(TransitionStep(LOCKSCREEN, AOD, 0f, STARTED))
+            steps.add(TransitionStep(LOCKSCREEN, AOD, 0.9f, RUNNING))
+            steps.add(TransitionStep(LOCKSCREEN, AOD, 1f, FINISHED))
+            steps.add(TransitionStep(AOD, GONE, 1f, STARTED))
+
+            steps.forEach { repository.sendTransitionStep(it) }
+
+            assertThat(finishedSteps).isEqualTo(listOf(steps[2], steps[5]))
+
+            job.cancel()
+        }
+
+    @Test
+    fun startedKeyguardTransitionStepTests() =
+        runBlocking(IMMEDIATE) {
+            var startedSteps = mutableListOf<TransitionStep>()
+            val job =
+                underTest.startedKeyguardTransitionStep
+                    .onEach { startedSteps.add(it) }
+                    .launchIn(this)
+
+            val steps = mutableListOf<TransitionStep>()
+
+            steps.add(TransitionStep(AOD, LOCKSCREEN, 0f, STARTED))
+            steps.add(TransitionStep(AOD, LOCKSCREEN, 0.5f, RUNNING))
+            steps.add(TransitionStep(AOD, LOCKSCREEN, 1f, FINISHED))
+            steps.add(TransitionStep(LOCKSCREEN, AOD, 0f, STARTED))
+            steps.add(TransitionStep(LOCKSCREEN, AOD, 0.9f, RUNNING))
+            steps.add(TransitionStep(LOCKSCREEN, AOD, 1f, FINISHED))
+            steps.add(TransitionStep(AOD, GONE, 1f, STARTED))
+
+            steps.forEach { repository.sendTransitionStep(it) }
+
+            assertThat(startedSteps).isEqualTo(listOf(steps[0], steps[3], steps[6]))
+
+            job.cancel()
+        }
+
+    companion object {
+        private val IMMEDIATE = Dispatchers.Main.immediate
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
index f9be067..78148c4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardBottomAreaViewModelTest.kt
@@ -23,16 +23,20 @@
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.doze.util.BurnInHelperWrapper
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.quickaffordance.BuiltInKeyguardQuickAffordanceKeys
 import com.android.systemui.keyguard.data.quickaffordance.FakeKeyguardQuickAffordanceConfig
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceConfig
+import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.KeyguardQuickAffordanceRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardBottomAreaInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
 import com.android.systemui.keyguard.domain.quickaffordance.FakeKeyguardQuickAffordanceRegistry
+import com.android.systemui.keyguard.shared.quickaffordance.ActivationState
 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
-import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordanceToggleState
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -41,6 +45,8 @@
 import com.google.common.truth.Truth.assertThat
 import kotlin.math.max
 import kotlin.math.min
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.test.runBlockingTest
@@ -82,20 +88,13 @@
             .thenReturn(RETURNED_BURN_IN_OFFSET)
 
         homeControlsQuickAffordanceConfig =
-            object :
-                FakeKeyguardQuickAffordanceConfig(
-                    BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS
-                ) {}
+            FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.HOME_CONTROLS)
         quickAccessWalletAffordanceConfig =
-            object :
-                FakeKeyguardQuickAffordanceConfig(
-                    BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
-                ) {}
+            FakeKeyguardQuickAffordanceConfig(
+                BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET
+            )
         qrCodeScannerAffordanceConfig =
-            object :
-                FakeKeyguardQuickAffordanceConfig(
-                    BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER
-                ) {}
+            FakeKeyguardQuickAffordanceConfig(BuiltInKeyguardQuickAffordanceKeys.QR_CODE_SCANNER)
         registry =
             FakeKeyguardQuickAffordanceRegistry(
                 mapOf(
@@ -116,6 +115,18 @@
         whenever(userTracker.userHandle).thenReturn(mock())
         whenever(lockPatternUtils.getStrongAuthForUser(anyInt()))
             .thenReturn(LockPatternUtils.StrongAuthTracker.STRONG_AUTH_NOT_REQUIRED)
+        val quickAffordanceRepository =
+            KeyguardQuickAffordanceRepository(
+                scope = CoroutineScope(IMMEDIATE),
+                backgroundDispatcher = IMMEDIATE,
+                selectionManager = KeyguardQuickAffordanceSelectionManager(),
+                configs =
+                    setOf(
+                        homeControlsQuickAffordanceConfig,
+                        quickAccessWalletAffordanceConfig,
+                        qrCodeScannerAffordanceConfig,
+                    ),
+            )
         underTest =
             KeyguardBottomAreaViewModel(
                 keyguardInteractor = keyguardInteractor,
@@ -127,6 +138,11 @@
                         keyguardStateController = keyguardStateController,
                         userTracker = userTracker,
                         activityStarter = activityStarter,
+                        featureFlags =
+                            FakeFeatureFlags().apply {
+                                set(Flags.CUSTOMIZABLE_LOCK_SCREEN_QUICK_AFFORDANCES, false)
+                            },
+                        repository = { quickAffordanceRepository },
                     ),
                 bottomAreaInteractor = KeyguardBottomAreaInteractor(repository = repository),
                 burnInHelperWrapper = burnInHelperWrapper,
@@ -508,28 +524,28 @@
                 KeyguardQuickAffordancePosition.BOTTOM_END -> quickAccessWalletAffordanceConfig
             }
 
-        val state =
+        val lockScreenState =
             if (testConfig.isVisible) {
                 if (testConfig.intent != null) {
-                    config.onClickedResult =
-                        KeyguardQuickAffordanceConfig.OnClickedResult.StartActivity(
+                    config.onTriggeredResult =
+                        KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity(
                             intent = testConfig.intent,
                             canShowWhileLocked = testConfig.canShowWhileLocked,
                         )
                 }
-                KeyguardQuickAffordanceConfig.State.Visible(
+                KeyguardQuickAffordanceConfig.LockScreenState.Visible(
                     icon = testConfig.icon ?: error("Icon is unexpectedly null!"),
-                    toggle =
+                    activationState =
                         when (testConfig.isActivated) {
-                            true -> KeyguardQuickAffordanceToggleState.On
-                            false -> KeyguardQuickAffordanceToggleState.Off
-                            null -> KeyguardQuickAffordanceToggleState.NotSupported
+                            true -> ActivationState.Active
+                            false -> ActivationState.Inactive
+                            null -> ActivationState.NotSupported
                         }
                 )
             } else {
-                KeyguardQuickAffordanceConfig.State.Hidden
+                KeyguardQuickAffordanceConfig.LockScreenState.Hidden
             }
-        config.setState(state)
+        config.setState(lockScreenState)
         return config.key
     }
 
@@ -576,5 +592,6 @@
     companion object {
         private const val DEFAULT_BURN_IN_OFFSET = 5
         private const val RETURNED_BURN_IN_OFFSET = 3
+        private val IMMEDIATE = Dispatchers.Main.immediate
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/ColorSchemeTransitionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/ColorSchemeTransitionTest.kt
index 5bb74e5..a8f4138 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/ColorSchemeTransitionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/ColorSchemeTransitionTest.kt
@@ -25,6 +25,7 @@
 import com.android.systemui.media.controls.models.GutsViewHolder
 import com.android.systemui.media.controls.models.player.MediaViewHolder
 import com.android.systemui.monet.ColorScheme
+import com.android.systemui.ripple.MultiRippleController
 import junit.framework.Assert.assertEquals
 import org.junit.After
 import org.junit.Before
@@ -60,6 +61,7 @@
     private lateinit var animatingColorTransitionFactory: AnimatingColorTransitionFactory
     @Mock private lateinit var mediaViewHolder: MediaViewHolder
     @Mock private lateinit var gutsViewHolder: GutsViewHolder
+    @Mock private lateinit var multiRippleController: MultiRippleController
 
     @JvmField @Rule val mockitoRule = MockitoJUnit.rule()
 
@@ -70,7 +72,12 @@
         whenever(extractColor.invoke(colorScheme)).thenReturn(TARGET_COLOR)
 
         colorSchemeTransition =
-            ColorSchemeTransition(context, mediaViewHolder, animatingColorTransitionFactory)
+            ColorSchemeTransition(
+                context,
+                mediaViewHolder,
+                multiRippleController,
+                animatingColorTransitionFactory
+            )
 
         colorTransition =
             object : AnimatingColorTransition(DEFAULT_COLOR, extractColor, applyColor) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
index 5843053..8190156 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaControlPanelTest.kt
@@ -59,6 +59,8 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.bluetooth.BroadcastDialogController
 import com.android.systemui.broadcast.BroadcastSender
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.media.controls.MediaTestUtils
 import com.android.systemui.media.controls.models.GutsViewHolder
 import com.android.systemui.media.controls.models.player.MediaAction
@@ -76,6 +78,7 @@
 import com.android.systemui.media.dialog.MediaOutputDialogFactory
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.ripple.MultiRippleView
 import com.android.systemui.statusbar.NotificationLockscreenUserManager
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.animation.TransitionLayout
@@ -174,6 +177,7 @@
     private lateinit var cancelText: TextView
     private lateinit var dismiss: FrameLayout
     private lateinit var dismissText: TextView
+    private lateinit var multiRippleView: MultiRippleView
 
     private lateinit var session: MediaSession
     private lateinit var device: MediaDeviceData
@@ -205,6 +209,8 @@
     private lateinit var recSubtitle2: TextView
     private lateinit var recSubtitle3: TextView
     private var shouldShowBroadcastButton: Boolean = false
+    private val fakeFeatureFlag =
+        FakeFeatureFlags().apply { this.set(Flags.UMO_SURFACE_RIPPLE, false) }
 
     @JvmField @Rule val mockito = MockitoJUnit.rule()
 
@@ -244,7 +250,8 @@
                     keyguardStateController,
                     activityIntentHelper,
                     lockscreenUserManager,
-                    broadcastDialogController
+                    broadcastDialogController,
+                    fakeFeatureFlag
                 ) {
                 override fun loadAnimator(
                     animId: Int,
@@ -374,6 +381,8 @@
                     )
             }
 
+        multiRippleView = MultiRippleView(context, null)
+
         whenever(viewHolder.player).thenReturn(view)
         whenever(viewHolder.appIcon).thenReturn(appIcon)
         whenever(viewHolder.albumView).thenReturn(albumView)
@@ -414,6 +423,8 @@
         whenever(viewHolder.getAction(R.id.action4)).thenReturn(action4)
 
         whenever(viewHolder.actionsTopBarrier).thenReturn(actionsTopBarrier)
+
+        whenever(viewHolder.multiRippleView).thenReturn(multiRippleView)
     }
 
     /** Initialize elements for the recommendation view holder */
@@ -1973,6 +1984,50 @@
         assertThat(expandedSet.getVisibility(recSubtitle3.id)).isEqualTo(ConstraintSet.GONE)
     }
 
+    @Test
+    fun onButtonClick_touchRippleFlagEnabled_playsTouchRipple() {
+        fakeFeatureFlag.set(Flags.UMO_SURFACE_RIPPLE, true)
+        val semanticActions =
+            MediaButton(
+                playOrPause =
+                    MediaAction(
+                        icon = null,
+                        action = {},
+                        contentDescription = "play",
+                        background = null
+                    )
+            )
+        val data = mediaData.copy(semanticActions = semanticActions)
+        player.attachPlayer(viewHolder)
+        player.bindPlayer(data, KEY)
+
+        viewHolder.actionPlayPause.callOnClick()
+
+        assertThat(viewHolder.multiRippleView.ripples.size).isEqualTo(1)
+    }
+
+    @Test
+    fun onButtonClick_touchRippleFlagDisabled_doesNotPlayTouchRipple() {
+        fakeFeatureFlag.set(Flags.UMO_SURFACE_RIPPLE, false)
+        val semanticActions =
+            MediaButton(
+                playOrPause =
+                    MediaAction(
+                        icon = null,
+                        action = {},
+                        contentDescription = "play",
+                        background = null
+                    )
+            )
+        val data = mediaData.copy(semanticActions = semanticActions)
+        player.attachPlayer(viewHolder)
+        player.bindPlayer(data, KEY)
+
+        viewHolder.actionPlayPause.callOnClick()
+
+        assertThat(viewHolder.multiRippleView.ripples.size).isEqualTo(0)
+    }
+
     private fun getScrubbingChangeListener(): SeekBarViewModel.ScrubbingChangeListener =
         withArgCaptor {
             verify(seekBarViewModel).setScrubbingChangeListener(capture())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
index 071604d..920801f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/controls/ui/MediaHierarchyManagerTest.kt
@@ -31,7 +31,7 @@
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.media.dream.MediaDreamComplication
 import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.shade.testing.FakeNotifPanelEvents
+import com.android.systemui.shade.ShadeExpansionStateManager
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.SysuiStatusBarStateController
 import com.android.systemui.statusbar.phone.KeyguardBypassController
@@ -89,7 +89,7 @@
     private lateinit var mediaHierarchyManager: MediaHierarchyManager
     private lateinit var mediaFrame: ViewGroup
     private val configurationController = FakeConfigurationController()
-    private val notifPanelEvents = FakeNotifPanelEvents()
+    private val notifPanelEvents = ShadeExpansionStateManager()
     private val settings = FakeSettings()
     private lateinit var testableLooper: TestableLooper
     private lateinit var fakeHandler: FakeHandler
@@ -346,7 +346,7 @@
 
     @Test
     fun isCurrentlyInGuidedTransformation_hostsVisible_expandImmediateEnabled_returnsFalse() {
-        notifPanelEvents.changeExpandImmediate(expandImmediate = true)
+        notifPanelEvents.notifyExpandImmediateChange(true)
         goToLockscreen()
         enterGuidedTransformation()
         whenever(lockHost.visible).thenReturn(true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
index fdeb3f5..ad19bc2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/media/taptotransfer/sender/MediaTttSenderCoordinatorTest.kt
@@ -45,6 +45,7 @@
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
+import com.android.systemui.temporarydisplay.chipbar.ChipbarLogger
 import com.android.systemui.temporarydisplay.chipbar.FakeChipbarCoordinator
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
@@ -80,6 +81,7 @@
     @Mock private lateinit var configurationController: ConfigurationController
     @Mock private lateinit var falsingManager: FalsingManager
     @Mock private lateinit var falsingCollector: FalsingCollector
+    @Mock private lateinit var chipbarLogger: ChipbarLogger
     @Mock private lateinit var logger: MediaTttLogger
     @Mock private lateinit var mediaTttFlags: MediaTttFlags
     @Mock private lateinit var packageManager: PackageManager
@@ -122,7 +124,7 @@
         chipbarCoordinator =
             FakeChipbarCoordinator(
                 context,
-                logger,
+                chipbarLogger,
                 windowManager,
                 fakeExecutor,
                 accessibilityManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index 6adce7a..c1fa9b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -61,6 +61,7 @@
 import android.view.DisplayInfo;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewRootImpl;
 import android.view.ViewTreeObserver;
 import android.view.WindowInsets;
 import android.view.WindowManager;
@@ -201,6 +202,8 @@
     private WakefulnessLifecycle mWakefulnessLifecycle;
     @Mock
     private Resources mResources;
+    @Mock
+    private ViewRootImpl mViewRootImpl;
     private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
     private DeviceConfigProxyFake mDeviceConfigProxyFake = new DeviceConfigProxyFake();
 
@@ -227,6 +230,7 @@
         when(mUserContextProvider.createCurrentUserContext(any(Context.class)))
                 .thenReturn(mContext);
         when(mNavigationBarView.getResources()).thenReturn(mResources);
+        when(mNavigationBarView.getViewRootImpl()).thenReturn(mViewRootImpl);
         setupSysuiDependency();
         // This class inflates views that call Dependency.get, thus these injections are still
         // necessary.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierTest.java
index 99a17a6..9115ab3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/carrier/QSCarrierTest.java
@@ -24,6 +24,7 @@
 import android.testing.TestableLooper;
 import android.view.LayoutInflater;
 import android.view.View;
+import android.widget.TextView;
 
 import androidx.test.filters.SmallTest;
 
@@ -48,6 +49,7 @@
     public void setUp() throws Exception {
         mTestableLooper = TestableLooper.get(this);
         LayoutInflater inflater = LayoutInflater.from(mContext);
+        mContext.ensureTestableResources();
         mTestableLooper.runWithLooper(() ->
                 mQSCarrier = (QSCarrier) inflater.inflate(R.layout.qs_carrier, null));
 
@@ -119,4 +121,30 @@
         mQSCarrier.updateState(c, true);
         assertEquals(View.GONE, mQSCarrier.getRSSIView().getVisibility());
     }
+
+    @Test
+    public void testCarrierNameMaxWidth_smallScreen_fromResource() {
+        int maxEms = 10;
+        mContext.getOrCreateTestableResources().addOverride(R.integer.qs_carrier_max_em, maxEms);
+        mContext.getOrCreateTestableResources()
+                .addOverride(R.bool.config_use_large_screen_shade_header, false);
+        TextView carrierText = mQSCarrier.requireViewById(R.id.qs_carrier_text);
+
+        mQSCarrier.onConfigurationChanged(mContext.getResources().getConfiguration());
+
+        assertEquals(maxEms, carrierText.getMaxEms());
+    }
+
+    @Test
+    public void testCarrierNameMaxWidth_largeScreen_maxInt() {
+        int maxEms = 10;
+        mContext.getOrCreateTestableResources().addOverride(R.integer.qs_carrier_max_em, maxEms);
+        mContext.getOrCreateTestableResources()
+                .addOverride(R.bool.config_use_large_screen_shade_header, true);
+        TextView carrierText = mQSCarrier.requireViewById(R.id.qs_carrier_text);
+
+        mQSCarrier.onConfigurationChanged(mContext.getResources().getConfiguration());
+
+        assertEquals(Integer.MAX_VALUE, carrierText.getMaxEms());
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
index 2c76be6..b067ee7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tileimpl/QSIconViewImplTest.java
@@ -24,6 +24,7 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
+import android.graphics.drawable.AnimatedVectorDrawable;
 import android.graphics.drawable.Drawable;
 import android.service.quicksettings.Tile;
 import android.testing.AndroidTestingRunner;
@@ -136,6 +137,20 @@
         assertEquals(mIconView.getColor(s1), mIconView.getColor(s2));
     }
 
+    @Test
+    public void testIconNotAnimatedWhenAllowAnimationsFalse() {
+        ImageView iv = new ImageView(mContext);
+        AnimatedVectorDrawable d = mock(AnimatedVectorDrawable.class);
+        State s = new State();
+        s.icon = mock(Icon.class);
+        when(s.icon.getDrawable(any())).thenReturn(d);
+        when(s.icon.getInvisibleDrawable(any())).thenReturn(d);
+
+        mIconView.updateIcon(iv, s, false);
+
+        verify(d, never()).start();
+    }
+
     private static Drawable.ConstantState fakeConstantState(Drawable otherDrawable) {
         return new Drawable.ConstantState() {
             @Override
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
index 4084cf4..3ae8428 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogTest.java
@@ -42,6 +42,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -52,6 +53,7 @@
 
 import java.util.List;
 
+@Ignore("b/257089187")
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ripple/MultiRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/ripple/MultiRippleControllerTest.kt
new file mode 100644
index 0000000..05512e5
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/ripple/MultiRippleControllerTest.kt
@@ -0,0 +1,104 @@
+/*
+ * Copyright (C) 2022 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.ripple
+
+import android.graphics.Color
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.ripple.MultiRippleController.Companion.MAX_RIPPLE_NUMBER
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class MultiRippleControllerTest : SysuiTestCase() {
+    private lateinit var multiRippleController: MultiRippleController
+    private lateinit var multiRippleView: MultiRippleView
+    private lateinit var rippleAnimationConfig: RippleAnimationConfig
+    private val fakeSystemClock = FakeSystemClock()
+
+    // FakeExecutor is needed to run animator.
+    private val fakeExecutor = FakeExecutor(fakeSystemClock)
+
+    @Before
+    fun setup() {
+        rippleAnimationConfig = RippleAnimationConfig(duration = 1000L)
+        multiRippleView = MultiRippleView(context, null)
+        multiRippleController = MultiRippleController(multiRippleView)
+    }
+
+    @Test
+    fun updateColor_updatesColor() {
+        val initialColor = Color.WHITE
+        val expectedColor = Color.RED
+
+        fakeExecutor.execute {
+            val rippleAnimation =
+                RippleAnimation(rippleAnimationConfig.apply { this.color = initialColor })
+
+            with(multiRippleController) {
+                play(rippleAnimation)
+                updateColor(expectedColor)
+            }
+
+            assertThat(rippleAnimationConfig.color).isEqualTo(expectedColor)
+        }
+    }
+
+    @Test
+    fun play_playsRipple() {
+        fakeExecutor.execute {
+            val rippleAnimation = RippleAnimation(rippleAnimationConfig)
+
+            multiRippleController.play(rippleAnimation)
+
+            assertThat(multiRippleView.ripples.size).isEqualTo(1)
+            assertThat(multiRippleView.ripples[0]).isEqualTo(rippleAnimation)
+        }
+    }
+
+    @Test
+    fun play_doesNotExceedMaxRipple() {
+        fakeExecutor.execute {
+            for (i in 0..MAX_RIPPLE_NUMBER + 10) {
+                multiRippleController.play(RippleAnimation(rippleAnimationConfig))
+            }
+
+            assertThat(multiRippleView.ripples.size).isEqualTo(MAX_RIPPLE_NUMBER)
+        }
+    }
+
+    @Test
+    fun play_onEnd_removesAnimation() {
+        fakeExecutor.execute {
+            val rippleAnimation = RippleAnimation(rippleAnimationConfig)
+            multiRippleController.play(rippleAnimation)
+
+            assertThat(multiRippleView.ripples.size).isEqualTo(1)
+            assertThat(multiRippleView.ripples[0]).isEqualTo(rippleAnimation)
+
+            fakeSystemClock.advanceTime(rippleAnimationConfig.duration)
+
+            assertThat(multiRippleView.ripples.size).isEqualTo(0)
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/ripple/RippleAnimationTest.kt b/packages/SystemUI/tests/src/com/android/systemui/ripple/RippleAnimationTest.kt
new file mode 100644
index 0000000..7662282
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/ripple/RippleAnimationTest.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2022 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.ripple
+
+import android.graphics.Color
+import android.testing.AndroidTestingRunner
+import androidx.core.graphics.ColorUtils
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class RippleAnimationTest : SysuiTestCase() {
+
+    private val fakeSystemClock = FakeSystemClock()
+    private val fakeExecutor = FakeExecutor(fakeSystemClock)
+
+    @Test
+    fun init_shaderHasCorrectConfig() {
+        val config =
+            RippleAnimationConfig(
+                duration = 3000L,
+                pixelDensity = 2f,
+                color = Color.RED,
+                opacity = 30,
+                shouldFillRipple = true,
+                sparkleStrength = 0.3f
+            )
+        val rippleAnimation = RippleAnimation(config)
+
+        with(rippleAnimation.rippleShader) {
+            assertThat(rippleFill).isEqualTo(config.shouldFillRipple)
+            assertThat(pixelDensity).isEqualTo(config.pixelDensity)
+            assertThat(color).isEqualTo(ColorUtils.setAlphaComponent(config.color, config.opacity))
+            assertThat(sparkleStrength).isEqualTo(config.sparkleStrength)
+        }
+    }
+
+    @Test
+    fun updateColor_updatesColorCorrectly() {
+        val initialColor = Color.WHITE
+        val expectedColor = Color.RED
+        val config = RippleAnimationConfig(color = initialColor)
+        val rippleAnimation = RippleAnimation(config)
+
+        fakeExecutor.execute {
+            with(rippleAnimation) {
+                play()
+                updateColor(expectedColor)
+            }
+
+            assertThat(config.color).isEqualTo(expectedColor)
+        }
+    }
+
+    @Test
+    fun play_updatesIsPlaying() {
+        val config = RippleAnimationConfig(duration = 1000L)
+        val rippleAnimation = RippleAnimation(config)
+
+        fakeExecutor.execute {
+            rippleAnimation.play()
+
+            assertThat(rippleAnimation.isPlaying()).isTrue()
+
+            // move time to finish the animation
+            fakeSystemClock.advanceTime(config.duration)
+
+            assertThat(rippleAnimation.isPlaying()).isFalse()
+        }
+    }
+
+    @Test
+    fun play_onEnd_triggersOnAnimationEnd() {
+        val config = RippleAnimationConfig(duration = 1000L)
+        val rippleAnimation = RippleAnimation(config)
+        var animationEnd = false
+
+        fakeExecutor.execute {
+            rippleAnimation.play(onAnimationEnd = { animationEnd = true })
+
+            fakeSystemClock.advanceTime(config.duration)
+
+            assertThat(animationEnd).isTrue()
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java
index 4c44dac..f4bc232 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/ImageExporterTest.java
@@ -23,6 +23,7 @@
 
 import static java.nio.charset.StandardCharsets.US_ASCII;
 
+import android.content.ContentProvider;
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.graphics.Bitmap;
@@ -31,9 +32,11 @@
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.Paint;
+import android.net.Uri;
 import android.os.Build;
 import android.os.ParcelFileDescriptor;
 import android.os.Process;
+import android.os.UserHandle;
 import android.provider.MediaStore;
 import android.testing.AndroidTestingRunner;
 
@@ -41,11 +44,18 @@
 import androidx.test.filters.MediumTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
 
 import com.google.common.util.concurrent.ListenableFuture;
 
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.MockitoAnnotations;
 
 import java.io.ByteArrayInputStream;
 import java.io.IOException;
@@ -60,7 +70,6 @@
 @RunWith(AndroidTestingRunner.class)
 @MediumTest // file I/O
 public class ImageExporterTest extends SysuiTestCase {
-
     /** Executes directly in the caller's thread */
     private static final Executor DIRECT_EXECUTOR = Runnable::run;
     private static final byte[] EXIF_FILE_TAG = "Exif\u0000\u0000".getBytes(US_ASCII);
@@ -68,6 +77,15 @@
     private static final ZonedDateTime CAPTURE_TIME =
             ZonedDateTime.of(LocalDateTime.of(2020, 12, 15, 13, 15), ZoneId.of("EST"));
 
+    private FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
+    @Mock
+    private ContentResolver mMockContentResolver;
+
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+    }
+
     @Test
     public void testImageFilename() {
         assertEquals("image file name", "Screenshot_20201215-131500.png",
@@ -92,7 +110,8 @@
     @Test
     public void testImageExport() throws ExecutionException, InterruptedException, IOException {
         ContentResolver contentResolver = mContext.getContentResolver();
-        ImageExporter exporter = new ImageExporter(contentResolver);
+        mFeatureFlags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, true);
+        ImageExporter exporter = new ImageExporter(contentResolver, mFeatureFlags);
 
         UUID requestId = UUID.fromString("3c11da99-9284-4863-b1d5-6f3684976814");
         Bitmap original = createCheckerBitmap(10, 10, 10);
@@ -168,6 +187,44 @@
                 values.getAsLong(MediaStore.MediaColumns.DATE_EXPIRES));
     }
 
+    @Test
+    public void testSetUser() {
+        mFeatureFlags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, true);
+        ImageExporter exporter = new ImageExporter(mMockContentResolver, mFeatureFlags);
+
+        UserHandle imageUserHande = UserHandle.of(10);
+
+        ArgumentCaptor<Uri> uriCaptor = ArgumentCaptor.forClass(Uri.class);
+        // Capture the URI and then return null to bail out of export.
+        Mockito.when(mMockContentResolver.insert(uriCaptor.capture(), Mockito.any())).thenReturn(
+                null);
+        exporter.export(DIRECT_EXECUTOR, UUID.fromString("3c11da99-9284-4863-b1d5-6f3684976814"),
+                null, CAPTURE_TIME, imageUserHande);
+
+        Uri expected = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
+        expected = ContentProvider.maybeAddUserId(expected, imageUserHande.getIdentifier());
+
+        assertEquals(expected, uriCaptor.getValue());
+    }
+
+    @Test
+    public void testSetUser_noWorkProfile() {
+        mFeatureFlags.set(Flags.SCREENSHOT_WORK_PROFILE_POLICY, false);
+        ImageExporter exporter = new ImageExporter(mMockContentResolver, mFeatureFlags);
+
+        UserHandle imageUserHandle = UserHandle.of(10);
+
+        ArgumentCaptor<Uri> uriCaptor = ArgumentCaptor.forClass(Uri.class);
+        // Capture the URI and then return null to bail out of export.
+        Mockito.when(mMockContentResolver.insert(uriCaptor.capture(), Mockito.any())).thenReturn(
+                null);
+        exporter.export(DIRECT_EXECUTOR, UUID.fromString("3c11da99-9284-4863-b1d5-6f3684976814"),
+                null, CAPTURE_TIME, imageUserHandle);
+
+        // The user handle should be ignored here since the flag is off.
+        assertEquals(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, uriCaptor.getValue());
+    }
+
     @SuppressWarnings("SameParameterValue")
     private Bitmap createCheckerBitmap(int tileSize, int w, int h) {
         Bitmap bitmap = Bitmap.createBitmap(w * tileSize, h * tileSize, Bitmap.Config.ARGB_8888);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
index 3a4da86..fa1fedb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
@@ -62,7 +62,7 @@
 import org.mockito.Mockito.`when` as whenever
 
 private const val USER_ID = 1
-private const val TASK_ID = 1
+private const val TASK_ID = 11
 
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 02f28a2..c98c1f2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -129,7 +129,6 @@
 import com.android.systemui.statusbar.StatusBarStateControllerImpl;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
 import com.android.systemui.statusbar.VibratorHelper;
-import com.android.systemui.statusbar.events.PrivacyDotViewController;
 import com.android.systemui.statusbar.notification.ConversationNotificationManager;
 import com.android.systemui.statusbar.notification.DynamicPrivacyController;
 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
@@ -153,7 +152,6 @@
 import com.android.systemui.statusbar.phone.KeyguardStatusBarView;
 import com.android.systemui.statusbar.phone.KeyguardStatusBarViewController;
 import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
-import com.android.systemui.statusbar.phone.NotificationIconAreaController;
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController;
 import com.android.systemui.statusbar.phone.ScrimController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -199,7 +197,6 @@
     @Mock private KeyguardBottomAreaView mKeyguardBottomArea;
     @Mock private KeyguardBottomAreaViewController mKeyguardBottomAreaViewController;
     @Mock private KeyguardBottomAreaView mQsFrame;
-    @Mock private NotificationIconAreaController mNotificationAreaController;
     @Mock private HeadsUpManagerPhone mHeadsUpManager;
     @Mock private NotificationShelfController mNotificationShelfController;
     @Mock private KeyguardStatusBarView mKeyguardStatusBar;
@@ -227,7 +224,7 @@
     @Mock private Resources mResources;
     @Mock private Configuration mConfiguration;
     @Mock private KeyguardClockSwitch mKeyguardClockSwitch;
-    @Mock private MediaHierarchyManager mMediaHiearchyManager;
+    @Mock private MediaHierarchyManager mMediaHierarchyManager;
     @Mock private ConversationNotificationManager mConversationNotificationManager;
     @Mock private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     @Mock private KeyguardStatusViewComponent.Factory mKeyguardStatusViewComponentFactory;
@@ -254,7 +251,6 @@
     @Mock private UiEventLogger mUiEventLogger;
     @Mock private LockIconViewController mLockIconViewController;
     @Mock private KeyguardMediaController mKeyguardMediaController;
-    @Mock private PrivacyDotViewController mPrivacyDotViewController;
     @Mock private NavigationModeController mNavigationModeController;
     @Mock private NavigationBarController mNavigationBarController;
     @Mock private LargeScreenShadeHeaderController mLargeScreenShadeHeaderController;
@@ -294,7 +290,7 @@
     private ConfigurationController mConfigurationController;
     private SysuiStatusBarStateController mStatusBarStateController;
     private NotificationPanelViewController mNotificationPanelViewController;
-    private View.AccessibilityDelegate mAccessibiltyDelegate;
+    private View.AccessibilityDelegate mAccessibilityDelegate;
     private NotificationsQuickSettingsContainer mNotificationContainerParent;
     private List<View.OnAttachStateChangeListener> mOnAttachStateChangeListeners;
     private Handler mMainHandler;
@@ -438,8 +434,6 @@
         when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState);
 
         mMainHandler = new Handler(Looper.getMainLooper());
-        NotificationPanelViewController.PanelEventsEmitter panelEventsEmitter =
-                new NotificationPanelViewController.PanelEventsEmitter();
 
         mNotificationPanelViewController = new NotificationPanelViewController(
                 mView,
@@ -458,7 +452,7 @@
                 mShadeLog,
                 mConfigurationController,
                 () -> flingAnimationUtilsBuilder, mStatusBarTouchableRegionManager,
-                mConversationNotificationManager, mMediaHiearchyManager,
+                mConversationNotificationManager, mMediaHierarchyManager,
                 mStatusBarKeyguardViewManager,
                 mNotificationsQSContainerController,
                 mNotificationStackScrollLayoutController,
@@ -467,7 +461,6 @@
                 mKeyguardUserSwitcherComponentFactory,
                 mKeyguardStatusBarViewComponentFactory,
                 mLockscreenShadeTransitionController,
-                mNotificationAreaController,
                 mAuthController,
                 mScrimController,
                 mUserManager,
@@ -476,7 +469,6 @@
                 mAmbientState,
                 mLockIconViewController,
                 mKeyguardMediaController,
-                mPrivacyDotViewController,
                 mTapAgainViewController,
                 mNavigationModeController,
                 mNavigationBarController,
@@ -494,8 +486,8 @@
                 mSysUiState,
                 () -> mKeyguardBottomAreaViewController,
                 mKeyguardUnlockAnimationController,
+                mKeyguardIndicationController,
                 mNotificationListContainer,
-                panelEventsEmitter,
                 mNotificationStackSizeCalculator,
                 mUnlockedScreenOffAnimationController,
                 mShadeTransitionController,
@@ -508,8 +500,6 @@
                 () -> {},
                 mNotificationShelfController);
         mNotificationPanelViewController.setHeadsUpManager(mHeadsUpManager);
-        mNotificationPanelViewController.setKeyguardIndicationController(
-                mKeyguardIndicationController);
         ArgumentCaptor<View.OnAttachStateChangeListener> onAttachStateChangeListenerArgumentCaptor =
                 ArgumentCaptor.forClass(View.OnAttachStateChangeListener.class);
         verify(mView, atLeast(1)).addOnAttachStateChangeListener(
@@ -519,9 +509,9 @@
         ArgumentCaptor<View.AccessibilityDelegate> accessibilityDelegateArgumentCaptor =
                 ArgumentCaptor.forClass(View.AccessibilityDelegate.class);
         verify(mView).setAccessibilityDelegate(accessibilityDelegateArgumentCaptor.capture());
-        mAccessibiltyDelegate = accessibilityDelegateArgumentCaptor.getValue();
+        mAccessibilityDelegate = accessibilityDelegateArgumentCaptor.getValue();
         mNotificationPanelViewController.getStatusBarStateController()
-                .addCallback(mNotificationPanelViewController.mStatusBarStateListener);
+                .addCallback(mNotificationPanelViewController.getStatusBarStateListener());
         mNotificationPanelViewController
                 .setHeadsUpAppearanceController(mock(HeadsUpAppearanceController.class));
         verify(mNotificationStackScrollLayoutController)
@@ -776,8 +766,8 @@
                 0L /* eventTime */, MotionEvent.ACTION_UP, 0f /* x */, 300f /* y */,
                 0 /* metaState */));
 
-        assertThat(mNotificationPanelViewController.getClosing()).isTrue();
-        assertThat(mNotificationPanelViewController.getIsFlinging()).isTrue();
+        assertThat(mNotificationPanelViewController.isClosing()).isTrue();
+        assertThat(mNotificationPanelViewController.isFlinging()).isTrue();
 
         // simulate touch that does not exceed touch slop
         onTouchEvent(MotionEvent.obtain(2L /* downTime */,
@@ -791,8 +781,8 @@
                 0 /* metaState */));
 
         // fling should still be called after a touch that does not exceed touch slop
-        assertThat(mNotificationPanelViewController.getClosing()).isTrue();
-        assertThat(mNotificationPanelViewController.getIsFlinging()).isTrue();
+        assertThat(mNotificationPanelViewController.isClosing()).isTrue();
+        assertThat(mNotificationPanelViewController.isFlinging()).isTrue();
     }
 
     @Test
@@ -847,7 +837,7 @@
     @Test
     public void testA11y_initializeNode() {
         AccessibilityNodeInfo nodeInfo = new AccessibilityNodeInfo();
-        mAccessibiltyDelegate.onInitializeAccessibilityNodeInfo(mView, nodeInfo);
+        mAccessibilityDelegate.onInitializeAccessibilityNodeInfo(mView, nodeInfo);
 
         List<AccessibilityNodeInfo.AccessibilityAction> actionList = nodeInfo.getActionList();
         assertThat(actionList).containsAtLeastElementsIn(
@@ -859,7 +849,7 @@
 
     @Test
     public void testA11y_scrollForward() {
-        mAccessibiltyDelegate.performAccessibilityAction(
+        mAccessibilityDelegate.performAccessibilityAction(
                 mView,
                 AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_FORWARD.getId(),
                 null);
@@ -869,7 +859,7 @@
 
     @Test
     public void testA11y_scrollUp() {
-        mAccessibiltyDelegate.performAccessibilityAction(
+        mAccessibilityDelegate.performAccessibilityAction(
                 mView,
                 AccessibilityNodeInfo.AccessibilityAction.ACTION_SCROLL_UP.getId(),
                 null);
@@ -1285,7 +1275,7 @@
         mNotificationPanelViewController.expandWithQs();
 
         verify(mLockscreenShadeTransitionController).goToLockedShade(
-                /* expandedView= */null, /* needsQSAnimation= */false);
+                /* expandedView= */null, /* needsQSAnimation= */true);
     }
 
     @Test
@@ -1332,11 +1322,11 @@
     public void testQsToBeImmediatelyExpandedWhenOpeningPanelInSplitShade() {
         enableSplitShade(/* enabled= */ true);
         mShadeExpansionStateManager.updateState(STATE_CLOSED);
-        assertThat(mNotificationPanelViewController.mQsExpandImmediate).isFalse();
+        assertThat(mNotificationPanelViewController.isQsExpandImmediate()).isFalse();
 
         mShadeExpansionStateManager.updateState(STATE_OPENING);
 
-        assertThat(mNotificationPanelViewController.mQsExpandImmediate).isTrue();
+        assertThat(mNotificationPanelViewController.isQsExpandImmediate()).isTrue();
     }
 
     @Test
@@ -1348,18 +1338,18 @@
         // going to lockscreen would trigger STATE_OPENING
         mShadeExpansionStateManager.updateState(STATE_OPENING);
 
-        assertThat(mNotificationPanelViewController.mQsExpandImmediate).isFalse();
+        assertThat(mNotificationPanelViewController.isQsExpandImmediate()).isFalse();
     }
 
     @Test
     public void testQsImmediateResetsWhenPanelOpensOrCloses() {
-        mNotificationPanelViewController.mQsExpandImmediate = true;
+        mNotificationPanelViewController.setQsExpandImmediate(true);
         mShadeExpansionStateManager.updateState(STATE_OPEN);
-        assertThat(mNotificationPanelViewController.mQsExpandImmediate).isFalse();
+        assertThat(mNotificationPanelViewController.isQsExpandImmediate()).isFalse();
 
-        mNotificationPanelViewController.mQsExpandImmediate = true;
+        mNotificationPanelViewController.setQsExpandImmediate(true);
         mShadeExpansionStateManager.updateState(STATE_CLOSED);
-        assertThat(mNotificationPanelViewController.mQsExpandImmediate).isFalse();
+        assertThat(mNotificationPanelViewController.isQsExpandImmediate()).isFalse();
     }
 
     @Test
@@ -1402,7 +1392,7 @@
 
     @Test
     public void interceptTouchEvent_withinQs_shadeExpanded_startsQsTracking() {
-        mNotificationPanelViewController.mQs = mQs;
+        mNotificationPanelViewController.setQs(mQs);
         when(mQsFrame.getX()).thenReturn(0f);
         when(mQsFrame.getWidth()).thenReturn(1000);
         when(mQsHeader.getTop()).thenReturn(0);
@@ -1422,7 +1412,7 @@
     @Test
     public void interceptTouchEvent_withinQs_shadeExpanded_inSplitShade_doesNotStartQsTracking() {
         enableSplitShade(true);
-        mNotificationPanelViewController.mQs = mQs;
+        mNotificationPanelViewController.setQs(mQs);
         when(mQsFrame.getX()).thenReturn(0f);
         when(mQsFrame.getWidth()).thenReturn(1000);
         when(mQsHeader.getTop()).thenReturn(0);
@@ -1498,7 +1488,7 @@
 
     @Test
     public void onLayoutChange_fullWidth_updatesQSWithFullWithTrue() {
-        mNotificationPanelViewController.mQs = mQs;
+        mNotificationPanelViewController.setQs(mQs);
 
         setIsFullWidth(true);
 
@@ -1507,7 +1497,7 @@
 
     @Test
     public void onLayoutChange_notFullWidth_updatesQSWithFullWithFalse() {
-        mNotificationPanelViewController.mQs = mQs;
+        mNotificationPanelViewController.setQs(mQs);
 
         setIsFullWidth(false);
 
@@ -1516,7 +1506,7 @@
 
     @Test
     public void onLayoutChange_qsNotSet_doesNotCrash() {
-        mNotificationPanelViewController.mQs = null;
+        mNotificationPanelViewController.setQs(null);
 
         triggerLayoutChange();
     }
@@ -1542,7 +1532,7 @@
     @Test
     public void setQsExpansion_lockscreenShadeTransitionInProgress_usesLockscreenSquishiness() {
         float squishinessFraction = 0.456f;
-        mNotificationPanelViewController.mQs = mQs;
+        mNotificationPanelViewController.setQs(mQs);
         when(mLockscreenShadeTransitionController.getQsSquishTransitionFraction())
                 .thenReturn(squishinessFraction);
         when(mNotificationStackScrollLayoutController.getNotificationSquishinessFraction())
@@ -1555,7 +1545,7 @@
                 /* delay= */ 0
         );
 
-        mNotificationPanelViewController.setQsExpansion(/* height= */ 123);
+        mNotificationPanelViewController.setQsExpansionHeight(/* height= */ 123);
 
         // First for setTransitionToFullShadeAmount and then setQsExpansion
         verify(mQs, times(2)).setQsExpansion(
@@ -1570,13 +1560,13 @@
     public void setQsExpansion_lockscreenShadeTransitionNotInProgress_usesStandardSquishiness() {
         float lsSquishinessFraction = 0.456f;
         float nsslSquishinessFraction = 0.987f;
-        mNotificationPanelViewController.mQs = mQs;
+        mNotificationPanelViewController.setQs(mQs);
         when(mLockscreenShadeTransitionController.getQsSquishTransitionFraction())
                 .thenReturn(lsSquishinessFraction);
         when(mNotificationStackScrollLayoutController.getNotificationSquishinessFraction())
                 .thenReturn(nsslSquishinessFraction);
 
-        mNotificationPanelViewController.setQsExpansion(/* height= */ 123);
+        mNotificationPanelViewController.setQsExpansionHeight(/* height= */ 123);
 
         verify(mQs).setQsExpansion(
                 /* expansion= */ anyFloat(),
@@ -1589,7 +1579,7 @@
     @Test
     public void onEmptySpaceClicked_notDozingAndOnKeyguard_requestsFaceAuth() {
         StatusBarStateController.StateListener statusBarStateListener =
-                mNotificationPanelViewController.mStatusBarStateListener;
+                mNotificationPanelViewController.getStatusBarStateListener();
         statusBarStateListener.onStateChanged(KEYGUARD);
         mNotificationPanelViewController.setDozing(false, false);
 
@@ -1597,17 +1587,17 @@
         mTouchHandler.onTouch(mock(View.class), mDownMotionEvent);
         mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0);
 
-        verify(mUpdateMonitor).requestFaceAuth(true,
+        verify(mUpdateMonitor).requestFaceAuth(
                 FaceAuthApiRequestReason.NOTIFICATION_PANEL_CLICKED);
     }
 
     @Test
     public void onEmptySpaceClicked_notDozingAndFaceDetectionIsNotRunning_startsUnlockAnimation() {
         StatusBarStateController.StateListener statusBarStateListener =
-                mNotificationPanelViewController.mStatusBarStateListener;
+                mNotificationPanelViewController.getStatusBarStateListener();
         statusBarStateListener.onStateChanged(KEYGUARD);
         mNotificationPanelViewController.setDozing(false, false);
-        when(mUpdateMonitor.requestFaceAuth(true, NOTIFICATION_PANEL_CLICKED)).thenReturn(false);
+        when(mUpdateMonitor.requestFaceAuth(NOTIFICATION_PANEL_CLICKED)).thenReturn(false);
 
         // This sets the dozing state that is read when onMiddleClicked is eventually invoked.
         mTouchHandler.onTouch(mock(View.class), mDownMotionEvent);
@@ -1619,10 +1609,10 @@
     @Test
     public void onEmptySpaceClicked_notDozingAndFaceDetectionIsRunning_doesNotStartUnlockHint() {
         StatusBarStateController.StateListener statusBarStateListener =
-                mNotificationPanelViewController.mStatusBarStateListener;
+                mNotificationPanelViewController.getStatusBarStateListener();
         statusBarStateListener.onStateChanged(KEYGUARD);
         mNotificationPanelViewController.setDozing(false, false);
-        when(mUpdateMonitor.requestFaceAuth(true, NOTIFICATION_PANEL_CLICKED)).thenReturn(true);
+        when(mUpdateMonitor.requestFaceAuth(NOTIFICATION_PANEL_CLICKED)).thenReturn(true);
 
         // This sets the dozing state that is read when onMiddleClicked is eventually invoked.
         mTouchHandler.onTouch(mock(View.class), mDownMotionEvent);
@@ -1634,7 +1624,7 @@
     @Test
     public void onEmptySpaceClicked_whenDozingAndOnKeyguard_doesNotRequestFaceAuth() {
         StatusBarStateController.StateListener statusBarStateListener =
-                mNotificationPanelViewController.mStatusBarStateListener;
+                mNotificationPanelViewController.getStatusBarStateListener();
         statusBarStateListener.onStateChanged(KEYGUARD);
         mNotificationPanelViewController.setDozing(true, false);
 
@@ -1642,18 +1632,18 @@
         mTouchHandler.onTouch(mock(View.class), mDownMotionEvent);
         mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0);
 
-        verify(mUpdateMonitor, never()).requestFaceAuth(anyBoolean(), anyString());
+        verify(mUpdateMonitor, never()).requestFaceAuth(anyString());
     }
 
     @Test
     public void onEmptySpaceClicked_whenStatusBarShadeLocked_doesNotRequestFaceAuth() {
         StatusBarStateController.StateListener statusBarStateListener =
-                mNotificationPanelViewController.mStatusBarStateListener;
+                mNotificationPanelViewController.getStatusBarStateListener();
         statusBarStateListener.onStateChanged(SHADE_LOCKED);
 
         mEmptySpaceClickListenerCaptor.getValue().onEmptySpaceClicked(0, 0);
 
-        verify(mUpdateMonitor, never()).requestFaceAuth(anyBoolean(), anyString());
+        verify(mUpdateMonitor, never()).requestFaceAuth(anyString());
 
     }
 
@@ -1667,11 +1657,11 @@
     public void onShadeFlingClosingEnd_mAmbientStateSetClose_thenOnExpansionStopped() {
         // Given: Shade is expanded
         mNotificationPanelViewController.notifyExpandingFinished();
-        mNotificationPanelViewController.setIsClosing(false);
+        mNotificationPanelViewController.setClosing(false);
 
         // When: Shade flings to close not canceled
         mNotificationPanelViewController.notifyExpandingStarted();
-        mNotificationPanelViewController.setIsClosing(true);
+        mNotificationPanelViewController.setClosing(true);
         mNotificationPanelViewController.onFlingEnd(false);
 
         // Then: AmbientState's mIsClosing should be set to false
@@ -1683,6 +1673,15 @@
         inOrder.verify(mNotificationStackScrollLayoutController).onExpansionStopped();
     }
 
+    @Test
+    public void inUnlockedSplitShade_transitioningMaxTransitionDistance_makesShadeFullyExpanded() {
+        mStatusBarStateController.setState(SHADE);
+        enableSplitShade(true);
+        int transitionDistance = mNotificationPanelViewController.getMaxPanelTransitionDistance();
+        mNotificationPanelViewController.setExpandedHeight(transitionDistance);
+        assertThat(mNotificationPanelViewController.isFullyExpanded()).isTrue();
+    }
+
     private static MotionEvent createMotionEvent(int x, int y, int action) {
         return MotionEvent.obtain(
                 /* downTime= */ 0, /* eventTime= */ 0, action, x, y, /* metaState= */ 0);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
index 26a0770..a4a7995 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowViewTest.java
@@ -152,7 +152,7 @@
 
         // WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept
         when(mStatusBarStateController.isDozing()).thenReturn(false);
-        when(mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()).thenReturn(true);
+        when(mStatusBarKeyguardViewManager.isShowingAlternateAuth()).thenReturn(true);
         when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false);
 
         // THEN we should intercept touch
@@ -165,7 +165,7 @@
 
         // WHEN not showing alt auth, not dozing, drag down helper doesn't want to intercept
         when(mStatusBarStateController.isDozing()).thenReturn(false);
-        when(mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()).thenReturn(false);
+        when(mStatusBarKeyguardViewManager.isShowingAlternateAuth()).thenReturn(false);
         when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false);
 
         // THEN we shouldn't intercept touch
@@ -178,7 +178,7 @@
 
         // WHEN showing alt auth, not dozing, drag down helper doesn't want to intercept
         when(mStatusBarStateController.isDozing()).thenReturn(false);
-        when(mStatusBarKeyguardViewManager.isShowingAlternateAuthOrAnimating()).thenReturn(true);
+        when(mStatusBarKeyguardViewManager.isShowingAlternateAuth()).thenReturn(true);
         when(mDragDownHelper.onInterceptTouchEvent(any())).thenReturn(false);
 
         // THEN we should handle the touch
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt
index 09add65..43c6942 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/PulsingGestureListenerTest.kt
@@ -66,6 +66,8 @@
     private lateinit var dumpManager: DumpManager
     @Mock
     private lateinit var statusBarStateController: StatusBarStateController
+    @Mock
+    private lateinit var shadeLogger: ShadeLogger
 
     private lateinit var tunableCaptor: ArgumentCaptor<Tunable>
     private lateinit var underTest: PulsingGestureListener
@@ -81,6 +83,7 @@
                 centralSurfaces,
                 ambientDisplayConfiguration,
                 statusBarStateController,
+                shadeLogger,
                 tunerService,
                 dumpManager
         )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/testing/FakeNotifPanelEvents.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/testing/FakeNotifPanelEvents.kt
deleted file mode 100644
index d052138..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/testing/FakeNotifPanelEvents.kt
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * Copyright (C) 2022 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.shade.testing
-
-import com.android.systemui.shade.NotifPanelEvents
-
-/** Fake implementation of [NotifPanelEvents] for testing. */
-class FakeNotifPanelEvents : NotifPanelEvents {
-
-    private val listeners = mutableListOf<NotifPanelEvents.Listener>()
-
-    override fun registerListener(listener: NotifPanelEvents.Listener) {
-        listeners.add(listener)
-    }
-
-    override fun unregisterListener(listener: NotifPanelEvents.Listener) {
-        listeners.remove(listener)
-    }
-
-    fun changeExpandImmediate(expandImmediate: Boolean) {
-        listeners.forEach { it.onExpandImmediateChanged(expandImmediate) }
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/regionsampling/RegionSamplingInstanceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/regionsampling/RegionSamplerTest.kt
similarity index 64%
rename from packages/SystemUI/tests/src/com/android/systemui/shared/regionsampling/RegionSamplingInstanceTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/shared/regionsampling/RegionSamplerTest.kt
index 09d51f6..5a62cc1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/regionsampling/RegionSamplingInstanceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/regionsampling/RegionSamplerTest.kt
@@ -21,61 +21,55 @@
 
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
-class RegionSamplingInstanceTest : SysuiTestCase() {
+class RegionSamplerTest : SysuiTestCase() {
 
-    @JvmField @Rule
-    val mockito = MockitoJUnit.rule()
+    @JvmField @Rule val mockito = MockitoJUnit.rule()
 
     @Mock private lateinit var sampledView: View
     @Mock private lateinit var mainExecutor: Executor
     @Mock private lateinit var bgExecutor: Executor
     @Mock private lateinit var regionSampler: RegionSamplingHelper
-    @Mock private lateinit var updateFun: RegionSamplingInstance.UpdateColorCallback
     @Mock private lateinit var pw: PrintWriter
     @Mock private lateinit var callback: RegionSamplingHelper.SamplingCallback
 
-    private lateinit var regionSamplingInstance: RegionSamplingInstance
+    private lateinit var mRegionSampler: RegionSampler
+    private var updateFun: UpdateColorCallback = {}
 
     @Before
     fun setUp() {
         whenever(sampledView.isAttachedToWindow).thenReturn(true)
-        whenever(regionSampler.callback).thenReturn(this@RegionSamplingInstanceTest.callback)
+        whenever(regionSampler.callback).thenReturn(this@RegionSamplerTest.callback)
 
-        regionSamplingInstance = object : RegionSamplingInstance(
-                sampledView,
-                mainExecutor,
-                bgExecutor,
-                true,
-                updateFun
-        ) {
-            override fun createRegionSamplingHelper(
+        mRegionSampler =
+            object : RegionSampler(sampledView, mainExecutor, bgExecutor, true, updateFun) {
+                override fun createRegionSamplingHelper(
                     sampledView: View,
                     callback: RegionSamplingHelper.SamplingCallback,
                     mainExecutor: Executor?,
                     bgExecutor: Executor?
-            ): RegionSamplingHelper {
-                return this@RegionSamplingInstanceTest.regionSampler
+                ): RegionSamplingHelper {
+                    return this@RegionSamplerTest.regionSampler
+                }
             }
-        }
     }
 
     @Test
     fun testStartRegionSampler() {
-        regionSamplingInstance.startRegionSampler()
+        mRegionSampler.startRegionSampler()
 
         verify(regionSampler).start(Rect(0, 0, 0, 0))
     }
 
     @Test
     fun testStopRegionSampler() {
-        regionSamplingInstance.stopRegionSampler()
+        mRegionSampler.stopRegionSampler()
 
         verify(regionSampler).stop()
     }
 
     @Test
     fun testDump() {
-        regionSamplingInstance.dump(pw)
+        mRegionSampler.dump(pw)
 
         verify(regionSampler).dump(pw)
     }
@@ -91,23 +85,18 @@
 
     @Test
     fun testFlagFalse() {
-        regionSamplingInstance = object : RegionSamplingInstance(
-                sampledView,
-                mainExecutor,
-                bgExecutor,
-                false,
-                updateFun
-        ) {
-            override fun createRegionSamplingHelper(
+        mRegionSampler =
+            object : RegionSampler(sampledView, mainExecutor, bgExecutor, false, updateFun) {
+                override fun createRegionSamplingHelper(
                     sampledView: View,
                     callback: RegionSamplingHelper.SamplingCallback,
                     mainExecutor: Executor?,
                     bgExecutor: Executor?
-            ): RegionSamplingHelper {
-                return this@RegionSamplingInstanceTest.regionSampler
+                ): RegionSamplingHelper {
+                    return this@RegionSamplerTest.regionSampler
+                }
             }
-        }
 
-        Assert.assertEquals(regionSamplingInstance.regionSampler, null)
+        Assert.assertEquals(mRegionSampler.regionSampler, null)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
index cf7f8dd..08933fc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/CommandQueueTest.java
@@ -33,9 +33,10 @@
 import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.IBiometricSysuiReceiver;
 import android.hardware.biometrics.PromptInfo;
-import android.hardware.fingerprint.IUdfpsHbmListener;
+import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
 import android.os.Bundle;
-import android.view.InsetsVisibilities;
+import android.view.WindowInsets;
+import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowInsetsController.Behavior;
 
@@ -135,27 +136,29 @@
     public void testOnSystemBarAttributesChanged() {
         doTestOnSystemBarAttributesChanged(DEFAULT_DISPLAY, 1,
                 new AppearanceRegion[]{new AppearanceRegion(2, new Rect())}, false,
-                BEHAVIOR_DEFAULT, new InsetsVisibilities(), "test", TEST_LETTERBOX_DETAILS);
+                BEHAVIOR_DEFAULT, WindowInsets.Type.defaultVisible(), "test",
+                TEST_LETTERBOX_DETAILS);
     }
 
     @Test
     public void testOnSystemBarAttributesChangedForSecondaryDisplay() {
         doTestOnSystemBarAttributesChanged(SECONDARY_DISPLAY, 1,
                 new AppearanceRegion[]{new AppearanceRegion(2, new Rect())}, false,
-                BEHAVIOR_DEFAULT, new InsetsVisibilities(), "test", TEST_LETTERBOX_DETAILS);
+                BEHAVIOR_DEFAULT, WindowInsets.Type.defaultVisible(), "test",
+                TEST_LETTERBOX_DETAILS);
     }
 
     private void doTestOnSystemBarAttributesChanged(int displayId, @Appearance int appearance,
             AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
-            @Behavior int behavior, InsetsVisibilities requestedVisibilities, String packageName,
+            @Behavior int behavior, @InsetsType int requestedVisibleTypes, String packageName,
             LetterboxDetails[] letterboxDetails) {
         mCommandQueue.onSystemBarAttributesChanged(displayId, appearance, appearanceRegions,
-                navbarColorManagedByIme, behavior, requestedVisibilities, packageName,
+                navbarColorManagedByIme, behavior, requestedVisibleTypes, packageName,
                 letterboxDetails);
         waitForIdleSync();
         verify(mCallbacks).onSystemBarAttributesChanged(eq(displayId), eq(appearance),
                 eq(appearanceRegions), eq(navbarColorManagedByIme), eq(behavior),
-                eq(requestedVisibilities), eq(packageName), eq(letterboxDetails));
+                eq(requestedVisibleTypes), eq(packageName), eq(letterboxDetails));
     }
 
     @Test
@@ -492,11 +495,12 @@
     }
 
     @Test
-    public void testSetUdfpsHbmListener() {
-        final IUdfpsHbmListener listener = mock(IUdfpsHbmListener.class);
-        mCommandQueue.setUdfpsHbmListener(listener);
+    public void testSetUdfpsRefreshRateCallback() {
+        final IUdfpsRefreshRateRequestCallback callback =
+                mock(IUdfpsRefreshRateRequestCallback.class);
+        mCommandQueue.setUdfpsRefreshRateCallback(callback);
         waitForIdleSync();
-        verify(mCallbacks).setUdfpsHbmListener(eq(listener));
+        verify(mCallbacks).setUdfpsRefreshRateCallback(eq(callback));
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
index 3ff7639..aa1114b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
@@ -34,6 +34,7 @@
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifLifetimeExtender.OnEndLifetimeExtensionCallback
+import com.android.systemui.statusbar.notification.collection.provider.LaunchFullScreenIntentProvider
 import com.android.systemui.statusbar.notification.collection.render.NodeController
 import com.android.systemui.statusbar.notification.interruption.HeadsUpViewBinder
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider
@@ -86,6 +87,7 @@
     private val mRemoteInputManager: NotificationRemoteInputManager = mock()
     private val mEndLifetimeExtension: OnEndLifetimeExtensionCallback = mock()
     private val mHeaderController: NodeController = mock()
+    private val mLaunchFullScreenIntentProvider: LaunchFullScreenIntentProvider = mock()
 
     private lateinit var mEntry: NotificationEntry
     private lateinit var mGroupSummary: NotificationEntry
@@ -110,6 +112,7 @@
             mHeadsUpViewBinder,
             mNotificationInterruptStateProvider,
             mRemoteInputManager,
+            mLaunchFullScreenIntentProvider,
             mHeaderController,
             mExecutor)
         mCoordinator.attach(mNotifPipeline)
@@ -242,6 +245,20 @@
     }
 
     @Test
+    fun testOnEntryAdded_shouldFullScreen() {
+        setShouldFullScreen(mEntry)
+        mCollectionListener.onEntryAdded(mEntry)
+        verify(mLaunchFullScreenIntentProvider).launchFullScreenIntent(mEntry)
+    }
+
+    @Test
+    fun testOnEntryAdded_shouldNotFullScreen() {
+        setShouldFullScreen(mEntry, should = false)
+        mCollectionListener.onEntryAdded(mEntry)
+        verify(mLaunchFullScreenIntentProvider, never()).launchFullScreenIntent(any())
+    }
+
+    @Test
     fun testPromotesAddedHUN() {
         // GIVEN the current entry should heads up
         whenever(mNotificationInterruptStateProvider.shouldHeadsUp(mEntry)).thenReturn(true)
@@ -406,6 +423,10 @@
 
         verify(mHeadsUpManager, never()).showNotification(mGroupSummary)
         verify(mHeadsUpManager).showNotification(mGroupSibling1)
+
+        // In addition make sure we have explicitly marked the summary as having interrupted due
+        // to the alert being transferred
+        assertTrue(mGroupSummary.hasInterrupted())
     }
 
     @Test
@@ -424,6 +445,7 @@
 
         verify(mHeadsUpManager, never()).showNotification(mGroupSummary)
         verify(mHeadsUpManager).showNotification(mGroupChild1)
+        assertTrue(mGroupSummary.hasInterrupted())
     }
 
     @Test
@@ -449,6 +471,7 @@
         verify(mHeadsUpManager, never()).showNotification(mGroupSummary)
         verify(mHeadsUpManager).showNotification(mGroupSibling1)
         verify(mHeadsUpManager, never()).showNotification(mGroupSibling2)
+        assertTrue(mGroupSummary.hasInterrupted())
     }
 
     @Test
@@ -474,6 +497,7 @@
         verify(mHeadsUpManager, never()).showNotification(mGroupSummary)
         verify(mHeadsUpManager).showNotification(mGroupChild1)
         verify(mHeadsUpManager, never()).showNotification(mGroupChild2)
+        assertTrue(mGroupSummary.hasInterrupted())
     }
 
     @Test
@@ -512,6 +536,7 @@
         verify(mHeadsUpManager).showNotification(mGroupPriority)
         verify(mHeadsUpManager, never()).showNotification(mGroupSibling1)
         verify(mHeadsUpManager, never()).showNotification(mGroupSibling2)
+        assertTrue(mGroupSummary.hasInterrupted())
     }
 
     @Test
@@ -548,6 +573,7 @@
         verify(mHeadsUpManager).showNotification(mGroupPriority)
         verify(mHeadsUpManager, never()).showNotification(mGroupSibling1)
         verify(mHeadsUpManager, never()).showNotification(mGroupSibling2)
+        assertTrue(mGroupSummary.hasInterrupted())
     }
 
     @Test
@@ -582,6 +608,7 @@
         verify(mHeadsUpManager).showNotification(mGroupPriority)
         verify(mHeadsUpManager, never()).showNotification(mGroupSibling1)
         verify(mHeadsUpManager, never()).showNotification(mGroupSibling2)
+        assertTrue(mGroupSummary.hasInterrupted())
     }
 
     @Test
@@ -672,6 +699,35 @@
     }
 
     @Test
+    fun testNoTransfer_groupSummaryNotAlerting() {
+        // When we have a group where the summary should not alert and exactly one child should
+        // alert, we should never mark the group summary as interrupted (because it doesn't).
+        setShouldHeadsUp(mGroupSummary, false)
+        setShouldHeadsUp(mGroupChild1, true)
+        setShouldHeadsUp(mGroupChild2, false)
+
+        mCollectionListener.onEntryAdded(mGroupSummary)
+        mCollectionListener.onEntryAdded(mGroupChild1)
+        mCollectionListener.onEntryAdded(mGroupChild2)
+        val groupEntry = GroupEntryBuilder()
+            .setSummary(mGroupSummary)
+            .setChildren(listOf(mGroupChild1, mGroupChild2))
+            .build()
+        mBeforeTransformGroupsListener.onBeforeTransformGroups(listOf(groupEntry))
+        verify(mHeadsUpViewBinder, never()).bindHeadsUpView(any(), any())
+        mBeforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry))
+
+        verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupSummary), any())
+        finishBind(mGroupChild1)
+        verify(mHeadsUpViewBinder, never()).bindHeadsUpView(eq(mGroupChild2), any())
+
+        verify(mHeadsUpManager, never()).showNotification(mGroupSummary)
+        verify(mHeadsUpManager).showNotification(mGroupChild1)
+        verify(mHeadsUpManager, never()).showNotification(mGroupChild2)
+        assertFalse(mGroupSummary.hasInterrupted())
+    }
+
+    @Test
     fun testOnRankingApplied_newEntryShouldAlert() {
         // GIVEN that mEntry has never interrupted in the past, and now should
         // and is new enough to do so
@@ -755,6 +811,11 @@
                 .thenReturn(should)
     }
 
+    private fun setShouldFullScreen(entry: NotificationEntry, should: Boolean = true) {
+        whenever(mNotificationInterruptStateProvider.shouldLaunchFullScreenIntentWhenAdded(entry))
+            .thenReturn(should)
+    }
+
     private fun finishBind(entry: NotificationEntry) {
         verify(mHeadsUpManager, never()).showNotification(entry)
         withArgCaptor<BindCallback> {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
index c961cec..b4a5f5c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/VisualStabilityCoordinatorTest.java
@@ -36,7 +36,8 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shade.NotifPanelEvents;
+import com.android.systemui.shade.ShadeStateEvents;
+import com.android.systemui.shade.ShadeStateEvents.ShadeStateEventsListener;
 import com.android.systemui.statusbar.notification.collection.GroupEntry;
 import com.android.systemui.statusbar.notification.collection.GroupEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
@@ -71,12 +72,12 @@
     @Mock private StatusBarStateController mStatusBarStateController;
     @Mock private Pluggable.PluggableListener<NotifStabilityManager> mInvalidateListener;
     @Mock private HeadsUpManager mHeadsUpManager;
-    @Mock private NotifPanelEvents mNotifPanelEvents;
+    @Mock private ShadeStateEvents mShadeStateEvents;
     @Mock private VisualStabilityProvider mVisualStabilityProvider;
 
     @Captor private ArgumentCaptor<WakefulnessLifecycle.Observer> mWakefulnessObserverCaptor;
     @Captor private ArgumentCaptor<StatusBarStateController.StateListener> mSBStateListenerCaptor;
-    @Captor private ArgumentCaptor<NotifPanelEvents.Listener> mNotifPanelEventsCallbackCaptor;
+    @Captor private ArgumentCaptor<ShadeStateEventsListener> mNotifPanelEventsCallbackCaptor;
     @Captor private ArgumentCaptor<NotifStabilityManager> mNotifStabilityManagerCaptor;
 
     private FakeSystemClock mFakeSystemClock = new FakeSystemClock();
@@ -84,7 +85,7 @@
 
     private WakefulnessLifecycle.Observer mWakefulnessObserver;
     private StatusBarStateController.StateListener mStatusBarStateListener;
-    private NotifPanelEvents.Listener mNotifPanelEventsCallback;
+    private ShadeStateEvents.ShadeStateEventsListener mNotifPanelEventsCallback;
     private NotifStabilityManager mNotifStabilityManager;
     private NotificationEntry mEntry;
     private GroupEntry mGroupEntry;
@@ -97,7 +98,7 @@
                 mFakeExecutor,
                 mDumpManager,
                 mHeadsUpManager,
-                mNotifPanelEvents,
+                mShadeStateEvents,
                 mStatusBarStateController,
                 mVisualStabilityProvider,
                 mWakefulnessLifecycle);
@@ -111,7 +112,8 @@
         verify(mStatusBarStateController).addCallback(mSBStateListenerCaptor.capture());
         mStatusBarStateListener = mSBStateListenerCaptor.getValue();
 
-        verify(mNotifPanelEvents).registerListener(mNotifPanelEventsCallbackCaptor.capture());
+        verify(mShadeStateEvents).addShadeStateEventsListener(
+                mNotifPanelEventsCallbackCaptor.capture());
         mNotifPanelEventsCallback = mNotifPanelEventsCallbackCaptor.getValue();
 
         verify(mNotifPipeline).setVisualStabilityManager(mNotifStabilityManagerCaptor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.java
deleted file mode 100644
index ab71264..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.java
+++ /dev/null
@@ -1,317 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.statusbar.notification.collection.render;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-
-import android.content.Context;
-import android.testing.AndroidTestingRunner;
-import android.view.View;
-import android.widget.FrameLayout;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-
-import java.util.List;
-
-@SmallTest
-@RunWith(AndroidTestingRunner.class)
-public class ShadeViewDifferTest extends SysuiTestCase {
-    private ShadeViewDiffer mDiffer;
-
-    private FakeController mRootController = new FakeController(mContext, "RootController");
-    private FakeController mController1 = new FakeController(mContext, "Controller1");
-    private FakeController mController2 = new FakeController(mContext, "Controller2");
-    private FakeController mController3 = new FakeController(mContext, "Controller3");
-    private FakeController mController4 = new FakeController(mContext, "Controller4");
-    private FakeController mController5 = new FakeController(mContext, "Controller5");
-    private FakeController mController6 = new FakeController(mContext, "Controller6");
-    private FakeController mController7 = new FakeController(mContext, "Controller7");
-
-    @Mock
-    ShadeViewDifferLogger mLogger;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-
-        mDiffer = new ShadeViewDiffer(mRootController, mLogger);
-    }
-
-    @Test
-    public void testAddInitialViews() {
-        // WHEN a spec is applied to an empty root
-        // THEN the final tree matches the spec
-        applySpecAndCheck(
-                node(mController1),
-                node(mController2,
-                        node(mController3),
-                        node(mController4)
-                ),
-                node(mController5)
-        );
-    }
-
-    @Test
-    public void testDetachViews() {
-        // GIVEN a preexisting tree of controllers
-        applySpecAndCheck(
-                node(mController1),
-                node(mController2,
-                        node(mController3),
-                        node(mController4)
-                ),
-                node(mController5)
-        );
-
-        // WHEN the new spec removes nodes
-        // THEN the final tree matches the spec
-        applySpecAndCheck(
-                node(mController5)
-        );
-    }
-
-    @Test
-    public void testReparentChildren() {
-        // GIVEN a preexisting tree of controllers
-        applySpecAndCheck(
-                node(mController1),
-                node(mController2,
-                        node(mController3),
-                        node(mController4)
-                ),
-                node(mController5)
-        );
-
-        // WHEN the parents of the controllers are all shuffled around
-        // THEN the final tree matches the spec
-        applySpecAndCheck(
-                node(mController1),
-                node(mController4),
-                node(mController3,
-                        node(mController2)
-                )
-        );
-    }
-
-    @Test
-    public void testReorderChildren() {
-        // GIVEN a preexisting tree of controllers
-        applySpecAndCheck(
-                node(mController1),
-                node(mController2),
-                node(mController3),
-                node(mController4)
-        );
-
-        // WHEN the children change order
-        // THEN the final tree matches the spec
-        applySpecAndCheck(
-                node(mController3),
-                node(mController2),
-                node(mController4),
-                node(mController1)
-        );
-    }
-
-    @Test
-    public void testRemovedGroupsAreBrokenApart() {
-        // GIVEN a preexisting tree with a group
-        applySpecAndCheck(
-                node(mController1),
-                node(mController2,
-                        node(mController3),
-                        node(mController4),
-                        node(mController5)
-                )
-        );
-
-        // WHEN the new spec removes the entire group
-        applySpecAndCheck(
-                node(mController1)
-        );
-
-        // THEN the group children are no longer attached to their parent
-        assertNull(mController3.getView().getParent());
-        assertNull(mController4.getView().getParent());
-        assertNull(mController5.getView().getParent());
-    }
-
-    @Test
-    public void testUnmanagedViews() {
-        // GIVEN a preexisting tree of controllers
-        applySpecAndCheck(
-                node(mController1),
-                node(mController2,
-                        node(mController3),
-                        node(mController4)
-                ),
-                node(mController5)
-        );
-
-        // GIVEN some additional unmanaged views attached to the tree
-        View unmanagedView1 = new View(mContext);
-        View unmanagedView2 = new View(mContext);
-
-        mRootController.getView().addView(unmanagedView1, 1);
-        mController2.getView().addView(unmanagedView2, 0);
-
-        // WHEN a new spec is applied with additional nodes
-        // THEN the final tree matches the spec
-        applySpecAndCheck(
-                node(mController1),
-                node(mController2,
-                        node(mController3),
-                        node(mController4),
-                        node(mController6)
-                ),
-                node(mController5),
-                node(mController7)
-        );
-
-        // THEN the unmanaged views have been pushed to the end of their parents
-        assertEquals(unmanagedView1, mRootController.view.getChildAt(4));
-        assertEquals(unmanagedView2, mController2.view.getChildAt(3));
-    }
-
-    private void applySpecAndCheck(NodeSpec spec) {
-        mDiffer.applySpec(spec);
-        checkMatchesSpec(spec);
-    }
-
-    private void applySpecAndCheck(SpecBuilder... children) {
-        applySpecAndCheck(node(mRootController, children).build());
-    }
-
-    private void checkMatchesSpec(NodeSpec spec) {
-        final NodeController parent = spec.getController();
-        final List<NodeSpec> children = spec.getChildren();
-
-        for (int i = 0; i < children.size(); i++) {
-            NodeSpec childSpec = children.get(i);
-            View view = parent.getChildAt(i);
-
-            assertEquals(
-                    "Child " + i + " of parent " + parent.getNodeLabel() + " should be "
-                            + childSpec.getController().getNodeLabel() + " but is instead "
-                            + (view != null ? mDiffer.getViewLabel(view) : "null"),
-                    view,
-                    childSpec.getController().getView());
-
-            if (!childSpec.getChildren().isEmpty()) {
-                checkMatchesSpec(childSpec);
-            }
-        }
-    }
-
-    private static class FakeController implements NodeController {
-
-        public final FrameLayout view;
-        private final String mLabel;
-
-        FakeController(Context context, String label) {
-            view = new FrameLayout(context);
-            mLabel = label;
-        }
-
-        @NonNull
-        @Override
-        public String getNodeLabel() {
-            return mLabel;
-        }
-
-        @NonNull
-        @Override
-        public FrameLayout getView() {
-            return view;
-        }
-
-        @Override
-        public int getChildCount() {
-            return view.getChildCount();
-        }
-
-        @Override
-        public View getChildAt(int index) {
-            return view.getChildAt(index);
-        }
-
-        @Override
-        public void addChildAt(@NonNull NodeController child, int index) {
-            view.addView(child.getView(), index);
-        }
-
-        @Override
-        public void moveChildTo(@NonNull NodeController child, int index) {
-            view.removeView(child.getView());
-            view.addView(child.getView(), index);
-        }
-
-        @Override
-        public void removeChild(@NonNull NodeController child, boolean isTransfer) {
-            view.removeView(child.getView());
-        }
-
-        @Override
-        public void onViewAdded() {
-        }
-
-        @Override
-        public void onViewMoved() {
-        }
-
-        @Override
-        public void onViewRemoved() {
-        }
-    }
-
-    private static class SpecBuilder {
-        private final NodeController mController;
-        private final SpecBuilder[] mChildren;
-
-        SpecBuilder(NodeController controller, SpecBuilder... children) {
-            mController = controller;
-            mChildren = children;
-        }
-
-        public NodeSpec build() {
-            return build(null);
-        }
-
-        public NodeSpec build(@Nullable NodeSpec parent) {
-            final NodeSpecImpl spec = new NodeSpecImpl(parent, mController);
-            for (SpecBuilder childBuilder : mChildren) {
-                spec.getChildren().add(childBuilder.build(spec));
-            }
-            return spec;
-        }
-    }
-
-    private static SpecBuilder node(NodeController controller, SpecBuilder... children) {
-        return new SpecBuilder(controller, children);
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.kt
new file mode 100644
index 0000000..15cf17d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/render/ShadeViewDifferTest.kt
@@ -0,0 +1,234 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.statusbar.notification.collection.render
+
+import android.content.Context
+import android.testing.AndroidTestingRunner
+import android.view.View
+import android.widget.FrameLayout
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.mock
+import org.junit.Assert
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+class ShadeViewDifferTest : SysuiTestCase() {
+    private lateinit var differ: ShadeViewDiffer
+    private val rootController = FakeController(mContext, "RootController")
+    private val controller1 = FakeController(mContext, "Controller1")
+    private val controller2 = FakeController(mContext, "Controller2")
+    private val controller3 = FakeController(mContext, "Controller3")
+    private val controller4 = FakeController(mContext, "Controller4")
+    private val controller5 = FakeController(mContext, "Controller5")
+    private val controller6 = FakeController(mContext, "Controller6")
+    private val controller7 = FakeController(mContext, "Controller7")
+    private val logger: ShadeViewDifferLogger = mock()
+
+    @Before
+    fun setUp() {
+        differ = ShadeViewDiffer(rootController, logger)
+    }
+
+    @Test
+    fun testAddInitialViews() {
+        // WHEN a spec is applied to an empty root
+        // THEN the final tree matches the spec
+        applySpecAndCheck(
+            node(controller1),
+            node(controller2, node(controller3), node(controller4)),
+            node(controller5)
+        )
+    }
+
+    @Test
+    fun testDetachViews() {
+        // GIVEN a preexisting tree of controllers
+        applySpecAndCheck(
+            node(controller1),
+            node(controller2, node(controller3), node(controller4)),
+            node(controller5)
+        )
+
+        // WHEN the new spec removes nodes
+        // THEN the final tree matches the spec
+        applySpecAndCheck(node(controller5))
+    }
+
+    @Test
+    fun testReparentChildren() {
+        // GIVEN a preexisting tree of controllers
+        applySpecAndCheck(
+            node(controller1),
+            node(controller2, node(controller3), node(controller4)),
+            node(controller5)
+        )
+
+        // WHEN the parents of the controllers are all shuffled around
+        // THEN the final tree matches the spec
+        applySpecAndCheck(
+            node(controller1),
+            node(controller4),
+            node(controller3, node(controller2))
+        )
+    }
+
+    @Test
+    fun testReorderChildren() {
+        // GIVEN a preexisting tree of controllers
+        applySpecAndCheck(
+            node(controller1),
+            node(controller2),
+            node(controller3),
+            node(controller4)
+        )
+
+        // WHEN the children change order
+        // THEN the final tree matches the spec
+        applySpecAndCheck(
+            node(controller3),
+            node(controller2),
+            node(controller4),
+            node(controller1)
+        )
+    }
+
+    @Test
+    fun testRemovedGroupsAreBrokenApart() {
+        // GIVEN a preexisting tree with a group
+        applySpecAndCheck(
+            node(controller1),
+            node(controller2, node(controller3), node(controller4), node(controller5))
+        )
+
+        // WHEN the new spec removes the entire group
+        applySpecAndCheck(node(controller1))
+
+        // THEN the group children are no longer attached to their parent
+        Assert.assertNull(controller3.view.parent)
+        Assert.assertNull(controller4.view.parent)
+        Assert.assertNull(controller5.view.parent)
+    }
+
+    @Test
+    fun testUnmanagedViews() {
+        // GIVEN a preexisting tree of controllers
+        applySpecAndCheck(
+            node(controller1),
+            node(controller2, node(controller3), node(controller4)),
+            node(controller5)
+        )
+
+        // GIVEN some additional unmanaged views attached to the tree
+        val unmanagedView1 = View(mContext)
+        val unmanagedView2 = View(mContext)
+        rootController.view.addView(unmanagedView1, 1)
+        controller2.view.addView(unmanagedView2, 0)
+
+        // WHEN a new spec is applied with additional nodes
+        // THEN the final tree matches the spec
+        applySpecAndCheck(
+            node(controller1),
+            node(controller2, node(controller3), node(controller4), node(controller6)),
+            node(controller5),
+            node(controller7)
+        )
+
+        // THEN the unmanaged views have been pushed to the end of their parents
+        Assert.assertEquals(unmanagedView1, rootController.view.getChildAt(4))
+        Assert.assertEquals(unmanagedView2, controller2.view.getChildAt(3))
+    }
+
+    private fun applySpecAndCheck(spec: NodeSpec) {
+        differ.applySpec(spec)
+        checkMatchesSpec(spec)
+    }
+
+    private fun applySpecAndCheck(vararg children: SpecBuilder) {
+        applySpecAndCheck(node(rootController, *children).build())
+    }
+
+    private fun checkMatchesSpec(spec: NodeSpec) {
+        val parent = spec.controller
+        val children = spec.children
+        for (i in children.indices) {
+            val childSpec = children[i]
+            val view = parent.getChildAt(i)
+            Assert.assertEquals(
+                "Child $i of parent ${parent.nodeLabel} " +
+                    "should be ${childSpec.controller.nodeLabel} " +
+                    "but instead " +
+                    view?.let(differ::getViewLabel),
+                view,
+                childSpec.controller.view
+            )
+            if (childSpec.children.isNotEmpty()) {
+                checkMatchesSpec(childSpec)
+            }
+        }
+    }
+
+    private class FakeController(context: Context, label: String) : NodeController {
+        override val view: FrameLayout = FrameLayout(context)
+        override val nodeLabel: String = label
+        override fun getChildCount(): Int = view.childCount
+
+        override fun getChildAt(index: Int): View? {
+            return view.getChildAt(index)
+        }
+
+        override fun addChildAt(child: NodeController, index: Int) {
+            view.addView(child.view, index)
+        }
+
+        override fun moveChildTo(child: NodeController, index: Int) {
+            view.removeView(child.view)
+            view.addView(child.view, index)
+        }
+
+        override fun removeChild(child: NodeController, isTransfer: Boolean) {
+            view.removeView(child.view)
+        }
+
+        override fun onViewAdded() {}
+        override fun onViewMoved() {}
+        override fun onViewRemoved() {}
+    }
+
+    private class SpecBuilder(
+        private val mController: NodeController,
+        private val children: Array<out SpecBuilder>
+    ) {
+
+        @JvmOverloads
+        fun build(parent: NodeSpec? = null): NodeSpec {
+            val spec = NodeSpecImpl(parent, mController)
+            for (childBuilder in children) {
+                spec.children.add(childBuilder.build(spec))
+            }
+            return spec
+        }
+    }
+
+    companion object {
+        private fun node(controller: NodeController, vararg children: SpecBuilder): SpecBuilder {
+            return SpecBuilder(controller, children)
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
index e7a435e..112e759 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotificationTestHelper.java
@@ -22,6 +22,7 @@
 
 import static com.android.systemui.statusbar.NotificationEntryHelper.modifyRanking;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -60,6 +61,7 @@
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.SmartReplyController;
 import com.android.systemui.statusbar.notification.ConversationNotificationProcessor;
+import com.android.systemui.statusbar.notification.SourceType;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.notifcollection.CommonNotifCollection;
@@ -192,6 +194,25 @@
     }
 
     /**
+     * Creates a generic row with rounded border.
+     *
+     * @return a generic row with the set roundness.
+     * @throws Exception
+     */
+    public ExpandableNotificationRow createRowWithRoundness(
+            float topRoundness,
+            float bottomRoundness,
+            SourceType sourceType
+    ) throws Exception {
+        ExpandableNotificationRow row = createRow();
+        row.requestTopRoundness(topRoundness, false, sourceType);
+        row.requestBottomRoundness(bottomRoundness, /*animate = */ false, sourceType);
+        assertEquals(topRoundness, row.getTopRoundness(), /* delta = */ 0f);
+        assertEquals(bottomRoundness, row.getBottomRoundness(), /* delta = */ 0f);
+        return row;
+    }
+
+    /**
      * Creates a generic row.
      *
      * @return a generic row with no special properties.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
index 7c41abba..438b528 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainerTest.java
@@ -25,6 +25,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.statusbar.notification.SourceType;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationTestHelper;
 
@@ -151,4 +152,37 @@
         Assert.assertNotNull("Children container must have a header after recreation",
                 mChildrenContainer.getCurrentHeaderView());
     }
+
+    @Test
+    public void addNotification_shouldResetOnScrollRoundness() throws Exception {
+        ExpandableNotificationRow row = mNotificationTestHelper.createRowWithRoundness(
+                /* topRoundness = */ 1f,
+                /* bottomRoundness = */ 1f,
+                /* sourceType = */ SourceType.OnScroll);
+
+        mChildrenContainer.addNotification(row, 0);
+
+        Assert.assertEquals(0f, row.getTopRoundness(), /* delta = */ 0f);
+        Assert.assertEquals(0f, row.getBottomRoundness(), /* delta = */ 0f);
+    }
+
+    @Test
+    public void addNotification_shouldNotResetOtherRoundness() throws Exception {
+        ExpandableNotificationRow row1 = mNotificationTestHelper.createRowWithRoundness(
+                /* topRoundness = */ 1f,
+                /* bottomRoundness = */ 1f,
+                /* sourceType = */ SourceType.DefaultValue);
+        ExpandableNotificationRow row2 = mNotificationTestHelper.createRowWithRoundness(
+                /* topRoundness = */ 1f,
+                /* bottomRoundness = */ 1f,
+                /* sourceType = */ SourceType.OnDismissAnimation);
+
+        mChildrenContainer.addNotification(row1, 0);
+        mChildrenContainer.addNotification(row2, 0);
+
+        Assert.assertEquals(1f, row1.getTopRoundness(), /* delta = */ 0f);
+        Assert.assertEquals(1f, row1.getBottomRoundness(), /* delta = */ 0f);
+        Assert.assertEquals(1f, row2.getTopRoundness(), /* delta = */ 0f);
+        Assert.assertEquals(1f, row2.getBottomRoundness(), /* delta = */ 0f);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
index 7741813..bda2336 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationShelfTest.kt
@@ -1,6 +1,7 @@
 package com.android.systemui.statusbar.notification.stack
 
 import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
 import android.testing.TestableLooper.RunWithLooper
 import androidx.test.filters.SmallTest
 import com.android.keyguard.BouncerPanelExpansionCalculator.aboutToShowBouncerProgress
@@ -8,8 +9,10 @@
 import com.android.systemui.animation.ShadeInterpolation
 import com.android.systemui.statusbar.NotificationShelf
 import com.android.systemui.statusbar.StatusBarIconView
+import com.android.systemui.statusbar.notification.SourceType
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow
 import com.android.systemui.statusbar.notification.row.ExpandableView
+import com.android.systemui.statusbar.notification.row.NotificationTestHelper
 import com.android.systemui.statusbar.notification.stack.StackScrollAlgorithm.StackScrollAlgorithmState
 import com.android.systemui.util.mockito.mock
 import junit.framework.Assert.assertEquals
@@ -37,6 +40,13 @@
     private val shelfState = shelf.viewState as NotificationShelf.ShelfState
     private val ambientState = mock(AmbientState::class.java)
     private val hostLayoutController: NotificationStackScrollLayoutController = mock()
+    private val notificationTestHelper by lazy {
+        allowTestableLooperAsMainThread()
+        NotificationTestHelper(
+                mContext,
+                mDependency,
+                TestableLooper.get(this))
+    }
 
     @Before
     fun setUp() {
@@ -299,6 +309,39 @@
         )
     }
 
+    @Test
+    fun resetOnScrollRoundness_shouldSetOnScrollTo0() {
+        val row: ExpandableNotificationRow = notificationTestHelper.createRowWithRoundness(
+                /* topRoundness = */ 1f,
+                /* bottomRoundness = */ 1f,
+                /* sourceType = */ SourceType.OnScroll)
+
+        NotificationShelf.resetOnScrollRoundness(row)
+
+        assertEquals(0f, row.topRoundness)
+        assertEquals(0f, row.bottomRoundness)
+    }
+
+    @Test
+    fun resetOnScrollRoundness_shouldNotResetOtherRoundness() {
+        val row1: ExpandableNotificationRow = notificationTestHelper.createRowWithRoundness(
+                /* topRoundness = */ 1f,
+                /* bottomRoundness = */ 1f,
+                /* sourceType = */ SourceType.DefaultValue)
+        val row2: ExpandableNotificationRow = notificationTestHelper.createRowWithRoundness(
+                /* topRoundness = */ 1f,
+                /* bottomRoundness = */ 1f,
+                /* sourceType = */ SourceType.OnDismissAnimation)
+
+        NotificationShelf.resetOnScrollRoundness(row1)
+        NotificationShelf.resetOnScrollRoundness(row2)
+
+        assertEquals(1f, row1.topRoundness)
+        assertEquals(1f, row1.bottomRoundness)
+        assertEquals(1f, row2.topRoundness)
+        assertEquals(1f, row2.bottomRoundness)
+    }
+
     private fun setFractionToShade(fraction: Float) {
         whenever(ambientState.fractionToShade).thenReturn(fraction)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index 4dea6be..b850cf1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -136,7 +136,7 @@
                 mAuthController, mStatusBarStateController,
                 mSessionTracker, mLatencyTracker, mScreenOffAnimationController, mVibratorHelper);
         mBiometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
-        mBiometricUnlockController.setBiometricModeListener(mBiometricModeListener);
+        mBiometricUnlockController.addBiometricModeListener(mBiometricModeListener);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
index e1f20d5..5ebaf69 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesCommandQueueCallbacksTest.java
@@ -29,7 +29,7 @@
 import android.os.PowerManager;
 import android.os.Vibrator;
 import android.testing.AndroidTestingRunner;
-import android.view.InsetsVisibilities;
+import android.view.WindowInsets;
 
 import androidx.test.filters.SmallTest;
 
@@ -177,7 +177,7 @@
         AppearanceRegion[] appearanceRegions = new AppearanceRegion[]{};
         boolean navbarColorManagedByIme = true;
         int behavior = 456;
-        InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
+        int requestedVisibleTypes = WindowInsets.Type.systemBars();
         String packageName = "test package name";
         LetterboxDetails[] letterboxDetails = new LetterboxDetails[]{};
 
@@ -187,7 +187,7 @@
                 appearanceRegions,
                 navbarColorManagedByIme,
                 behavior,
-                requestedVisibilities,
+                requestedVisibleTypes,
                 packageName,
                 letterboxDetails);
 
@@ -197,7 +197,7 @@
                 appearanceRegions,
                 navbarColorManagedByIme,
                 behavior,
-                requestedVisibilities,
+                requestedVisibleTypes,
                 packageName,
                 letterboxDetails
         );
@@ -209,7 +209,7 @@
         AppearanceRegion[] appearanceRegions = new AppearanceRegion[]{};
         boolean navbarColorManagedByIme = true;
         int behavior = 456;
-        InsetsVisibilities requestedVisibilities = new InsetsVisibilities();
+        int requestedVisibleTypes = WindowInsets.Type.systemBars();
         String packageName = "test package name";
         LetterboxDetails[] letterboxDetails = new LetterboxDetails[]{};
 
@@ -219,7 +219,7 @@
                 appearanceRegions,
                 navbarColorManagedByIme,
                 behavior,
-                requestedVisibilities,
+                requestedVisibleTypes,
                 packageName,
                 letterboxDetails);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
index 5755782..7ce3a67 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/CentralSurfacesImplTest.java
@@ -500,7 +500,7 @@
                 mKeyguardVieMediatorCallback);
 
         // TODO: we should be able to call mCentralSurfaces.start() and have all the below values
-        // initialized automatically.
+        // initialized automatically and make NPVC private.
         mCentralSurfaces.mNotificationShadeWindowView = mNotificationShadeWindowView;
         mCentralSurfaces.mNotificationPanelViewController = mNotificationPanelViewController;
         mCentralSurfaces.mDozeScrimController = mDozeScrimController;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java
index fca9771..287ebba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/LightsOutNotifControllerTest.java
@@ -30,6 +30,7 @@
 import android.view.Display;
 import android.view.View;
 import android.view.ViewPropertyAnimator;
+import android.view.WindowInsets;
 import android.view.WindowManager;
 
 import androidx.lifecycle.Observer;
@@ -107,7 +108,7 @@
                 null /* appearanceRegions */,
                 false /* navbarColorManagedByIme */,
                 BEHAVIOR_DEFAULT,
-                null /* requestedVisibilities */,
+                WindowInsets.Type.defaultVisible(),
                 null /* packageName */,
                 null /* letterboxDetails */);
         assertTrue(mLightsOutNotifController.areLightsOut());
@@ -121,7 +122,7 @@
                 null /* appearanceRegions */,
                 false /* navbarColorManagedByIme */,
                 BEHAVIOR_DEFAULT,
-                null /* requestedVisibilities */,
+                WindowInsets.Type.defaultVisible(),
                 null /* packageName */,
                 null /* letterboxDetails */);
         assertFalse(mLightsOutNotifController.areLightsOut());
@@ -153,7 +154,7 @@
                 null /* appearanceRegions */,
                 false /* navbarColorManagedByIme */,
                 BEHAVIOR_DEFAULT,
-                null /* requestedVisibilities */,
+                WindowInsets.Type.defaultVisible(),
                 null /* packageName */,
                 null /* letterboxDetails */);
 
@@ -174,7 +175,7 @@
                 null /* appearanceRegions */,
                 false /* navbarColorManagedByIme */,
                 BEHAVIOR_DEFAULT,
-                null /* requestedVisibilities */,
+                WindowInsets.Type.defaultVisible(),
                 null /* packageName */,
                 null /* letterboxDetails */);
 
@@ -195,7 +196,7 @@
                 null /* appearanceRegions */,
                 false /* navbarColorManagedByIme */,
                 BEHAVIOR_DEFAULT,
-                null /* requestedVisibilities */,
+                WindowInsets.Type.defaultVisible(),
                 null /* packageName */,
                 null /* letterboxDetails */);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
index 9c56c26..6fb6893 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarIconControllerTest.java
@@ -45,7 +45,7 @@
 import com.android.systemui.statusbar.phone.StatusBarSignalPolicy.WifiIconState;
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags;
 import com.android.systemui.statusbar.pipeline.mobile.ui.MobileUiAdapter;
-import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel;
+import com.android.systemui.statusbar.pipeline.wifi.ui.WifiUiAdapter;
 import com.android.systemui.utils.leaks.LeakCheckedTest;
 
 import org.junit.Before;
@@ -80,7 +80,7 @@
                 layout,
                 StatusBarLocation.HOME,
                 mock(StatusBarPipelineFlags.class),
-                mock(WifiViewModel.class),
+                mock(WifiUiAdapter.class),
                 mock(MobileUiAdapter.class),
                 mMobileContextProvider,
                 mock(DarkIconDispatcher.class));
@@ -124,14 +124,14 @@
                 LinearLayout group,
                 StatusBarLocation location,
                 StatusBarPipelineFlags statusBarPipelineFlags,
-                WifiViewModel wifiViewModel,
+                WifiUiAdapter wifiUiAdapter,
                 MobileUiAdapter mobileUiAdapter,
                 MobileContextProvider contextProvider,
                 DarkIconDispatcher darkIconDispatcher) {
             super(group,
                     location,
                     statusBarPipelineFlags,
-                    wifiViewModel,
+                    wifiUiAdapter,
                     mobileUiAdapter,
                     contextProvider,
                     darkIconDispatcher);
@@ -172,7 +172,7 @@
             super(group,
                     StatusBarLocation.HOME,
                     mock(StatusBarPipelineFlags.class),
-                    mock(WifiViewModel.class),
+                    mock(WifiUiAdapter.class),
                     mock(MobileUiAdapter.class),
                     contextProvider);
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 0c35659..7166666 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -307,7 +307,7 @@
 
     @Test
     public void onPanelExpansionChanged_neverTranslatesBouncerWhenLaunchingApp() {
-        when(mCentralSurfaces.isInLaunchTransition()).thenReturn(true);
+        when(mNotificationPanelView.isLaunchTransitionFinished()).thenReturn(true);
         mStatusBarKeyguardViewManager.onPanelExpansionChanged(
                 expansionEvent(
                         /* fraction= */ KeyguardBouncer.EXPANSION_VISIBLE,
@@ -361,7 +361,7 @@
 
     @Test
     public void setOccluded_isInLaunchTransition_onKeyguardOccludedChangedCalled() {
-        when(mCentralSurfaces.isInLaunchTransition()).thenReturn(true);
+        when(mNotificationPanelView.isLaunchTransitionFinished()).thenReturn(true);
         mStatusBarKeyguardViewManager.show(null);
 
         mStatusBarKeyguardViewManager.setOccluded(true /* occluded */, false /* animated */);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
index c409857..ce54d78 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarterTest.java
@@ -67,8 +67,8 @@
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
 import com.android.systemui.statusbar.StatusBarState;
 import com.android.systemui.statusbar.notification.NotificationLaunchAnimatorControllerProvider;
-import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
+import com.android.systemui.statusbar.notification.collection.provider.LaunchFullScreenIntentProvider;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
@@ -123,8 +123,6 @@
     @Mock
     private ShadeControllerImpl mShadeController;
     @Mock
-    private NotifPipeline mNotifPipeline;
-    @Mock
     private NotificationVisibilityProvider mVisibilityProvider;
     @Mock
     private ActivityIntentHelper mActivityIntentHelper;
@@ -197,7 +195,6 @@
                         getContext(),
                         mHandler,
                         mUiBgExecutor,
-                        mNotifPipeline,
                         mVisibilityProvider,
                         headsUpManager,
                         mActivityStarter,
@@ -222,7 +219,8 @@
                         mock(NotificationPresenter.class),
                         mock(NotificationPanelViewController.class),
                         mActivityLaunchAnimator,
-                        notificationAnimationProvider
+                        notificationAnimationProvider,
+                        mock(LaunchFullScreenIntentProvider.class)
                 );
 
         // set up dismissKeyguardThenExecute to synchronously invoke the OnDismissAction arg
@@ -384,11 +382,9 @@
         NotificationEntry entry = mock(NotificationEntry.class);
         when(entry.getImportance()).thenReturn(NotificationManager.IMPORTANCE_HIGH);
         when(entry.getSbn()).thenReturn(sbn);
-        when(mNotificationInterruptStateProvider.shouldLaunchFullScreenIntentWhenAdded(eq(entry)))
-                .thenReturn(true);
 
         // WHEN
-        mNotificationActivityStarter.handleFullScreenIntent(entry);
+        mNotificationActivityStarter.launchFullScreenIntent(entry);
 
         // THEN display should try wake up for the full screen intent
         verify(mCentralSurfaces).wakeUpForFullScreenIntent();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemBarAttributesListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemBarAttributesListenerTest.kt
index 9957c2a..4d79a53 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemBarAttributesListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/SystemBarAttributesListenerTest.kt
@@ -3,12 +3,9 @@
 import android.graphics.Rect
 import android.testing.AndroidTestingRunner
 import android.view.Display
-import android.view.InsetsVisibilities
+import android.view.WindowInsets
 import android.view.WindowInsetsController
-import android.view.WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS
-import android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
-import android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS
-import android.view.WindowInsetsController.Appearance
+import android.view.WindowInsetsController.*
 import androidx.test.filters.SmallTest
 import com.android.internal.statusbar.LetterboxDetails
 import com.android.internal.view.AppearanceRegion
@@ -27,8 +24,8 @@
 import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyZeroInteractions
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -88,7 +85,7 @@
         changeSysBarAttrs(TEST_APPEARANCE)
 
         verify(statusBarStateController)
-            .setSystemBarAttributes(eq(TEST_APPEARANCE), anyInt(), any(), any())
+            .setSystemBarAttributes(eq(TEST_APPEARANCE), anyInt(), anyInt(), any())
     }
 
     @Test
@@ -97,7 +94,7 @@
 
         verify(statusBarStateController)
             .setSystemBarAttributes(
-                eq(TEST_LETTERBOX_APPEARANCE.appearance), anyInt(), any(), any())
+                eq(TEST_LETTERBOX_APPEARANCE.appearance), anyInt(), anyInt(), any())
     }
 
     @Test
@@ -130,7 +127,7 @@
 
         verify(statusBarStateController)
             .setSystemBarAttributes(
-                eq(TEST_LETTERBOX_APPEARANCE.appearance), anyInt(), any(), any())
+                eq(TEST_LETTERBOX_APPEARANCE.appearance), anyInt(), anyInt(), any())
     }
 
     @Test
@@ -197,7 +194,7 @@
             appearanceRegions,
             /* navbarColorManagedByIme= */ false,
             WindowInsetsController.BEHAVIOR_DEFAULT,
-            InsetsVisibilities(),
+            WindowInsets.Type.defaultVisible(),
             "package name",
             letterboxDetails)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
index 6ff7b7c..288f54c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionRepository.kt
@@ -17,14 +17,27 @@
 package com.android.systemui.statusbar.pipeline.mobile.data.repository
 
 import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
-import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 
 class FakeMobileConnectionRepository : MobileConnectionRepository {
     private val _subscriptionsModelFlow = MutableStateFlow(MobileSubscriptionModel())
-    override val subscriptionModelFlow: Flow<MobileSubscriptionModel> = _subscriptionsModelFlow
+    override val subscriptionModelFlow = _subscriptionsModelFlow
+
+    private val _dataEnabled = MutableStateFlow(true)
+    override val dataEnabled = _dataEnabled
+
+    private val _isDefaultDataSubscription = MutableStateFlow(true)
+    override val isDefaultDataSubscription = _isDefaultDataSubscription
 
     fun setMobileSubscriptionModel(model: MobileSubscriptionModel) {
         _subscriptionsModelFlow.value = model
     }
+
+    fun setDataEnabled(enabled: Boolean) {
+        _dataEnabled.value = enabled
+    }
+
+    fun setIsDefaultDataSubscription(isDefault: Boolean) {
+        _isDefaultDataSubscription.value = isDefault
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
index c88d468..533d5d9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeMobileConnectionsRepository.kt
@@ -17,8 +17,9 @@
 package com.android.systemui.statusbar.pipeline.mobile.data.repository
 
 import android.telephony.SubscriptionInfo
-import android.telephony.SubscriptionManager
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
 import com.android.settingslib.mobile.MobileMappings.Config
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 
@@ -26,18 +27,26 @@
     private val _subscriptionsFlow = MutableStateFlow<List<SubscriptionInfo>>(listOf())
     override val subscriptionsFlow: Flow<List<SubscriptionInfo>> = _subscriptionsFlow
 
-    private val _activeMobileDataSubscriptionId =
-        MutableStateFlow(SubscriptionManager.INVALID_SUBSCRIPTION_ID)
+    private val _activeMobileDataSubscriptionId = MutableStateFlow(INVALID_SUBSCRIPTION_ID)
     override val activeMobileDataSubscriptionId = _activeMobileDataSubscriptionId
 
     private val _defaultDataSubRatConfig = MutableStateFlow(Config())
     override val defaultDataSubRatConfig = _defaultDataSubRatConfig
 
+    private val _defaultDataSubId = MutableStateFlow(INVALID_SUBSCRIPTION_ID)
+    override val defaultDataSubId = _defaultDataSubId
+
+    private val _mobileConnectivity = MutableStateFlow(MobileConnectivityModel())
+    override val defaultMobileNetworkConnectivity = _mobileConnectivity
+
     private val subIdRepos = mutableMapOf<Int, MobileConnectionRepository>()
     override fun getRepoForSubId(subId: Int): MobileConnectionRepository {
         return subIdRepos[subId] ?: FakeMobileConnectionRepository().also { subIdRepos[subId] = it }
     }
 
+    private val _globalMobileDataSettingChangedEvent = MutableStateFlow(Unit)
+    override val globalMobileDataSettingChangedEvent = _globalMobileDataSettingChangedEvent
+
     fun setSubscriptions(subs: List<SubscriptionInfo>) {
         _subscriptionsFlow.value = subs
     }
@@ -46,11 +55,23 @@
         _defaultDataSubRatConfig.value = config
     }
 
+    fun setDefaultDataSubId(id: Int) {
+        _defaultDataSubId.value = id
+    }
+
+    fun setMobileConnectivity(model: MobileConnectivityModel) {
+        _mobileConnectivity.value = model
+    }
+
+    suspend fun triggerGlobalMobileDataSettingChangedEvent() {
+        _globalMobileDataSettingChangedEvent.emit(Unit)
+    }
+
     fun setActiveMobileDataSubscriptionId(subId: Int) {
         _activeMobileDataSubscriptionId.value = subId
     }
 
-    fun setMobileConnectionRepositoryForId(subId: Int, repo: MobileConnectionRepository) {
-        subIdRepos[subId] = repo
+    fun setMobileConnectionRepositoryMap(connections: Map<Int, MobileConnectionRepository>) {
+        connections.forEach { entry -> subIdRepos[entry.key] = entry.value }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeUserSetupRepository.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeUserSetupRepository.kt
index 6c495c5..141b50c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeUserSetupRepository.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/FakeUserSetupRepository.kt
@@ -16,13 +16,12 @@
 
 package com.android.systemui.statusbar.pipeline.mobile.data.repository
 
-import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 
 /** Defaults to `true` */
 class FakeUserSetupRepository : UserSetupRepository {
     private val _isUserSetup: MutableStateFlow<Boolean> = MutableStateFlow(true)
-    override val isUserSetupFlow: Flow<Boolean> = _isUserSetup
+    override val isUserSetupFlow = _isUserSetup
 
     fun setUserSetup(setup: Boolean) {
         _isUserSetup.value = setup
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryTest.kt
index 775e6db..5ce51bb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionRepositoryTest.kt
@@ -16,20 +16,26 @@
 
 package com.android.systemui.statusbar.pipeline.mobile.data.repository
 
+import android.os.UserHandle
+import android.provider.Settings
 import android.telephony.CellSignalStrengthCdma
 import android.telephony.ServiceState
 import android.telephony.SignalStrength
 import android.telephony.SubscriptionInfo
-import android.telephony.SubscriptionManager
 import android.telephony.TelephonyCallback
 import android.telephony.TelephonyCallback.ServiceStateListener
 import android.telephony.TelephonyDisplayInfo
 import android.telephony.TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_LTE_CA
 import android.telephony.TelephonyManager
+import android.telephony.TelephonyManager.DATA_CONNECTED
+import android.telephony.TelephonyManager.DATA_CONNECTING
+import android.telephony.TelephonyManager.DATA_DISCONNECTED
+import android.telephony.TelephonyManager.DATA_DISCONNECTING
 import android.telephony.TelephonyManager.NETWORK_TYPE_LTE
 import android.telephony.TelephonyManager.NETWORK_TYPE_UNKNOWN
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
 import com.android.systemui.statusbar.pipeline.mobile.data.model.DefaultNetworkType
 import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType
@@ -38,6 +44,7 @@
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.settings.FakeSettings
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
@@ -59,21 +66,27 @@
 class MobileConnectionRepositoryTest : SysuiTestCase() {
     private lateinit var underTest: MobileConnectionRepositoryImpl
 
-    @Mock private lateinit var subscriptionManager: SubscriptionManager
     @Mock private lateinit var telephonyManager: TelephonyManager
     @Mock private lateinit var logger: ConnectivityPipelineLogger
 
     private val scope = CoroutineScope(IMMEDIATE)
+    private val globalSettings = FakeSettings()
+    private val connectionsRepo = FakeMobileConnectionsRepository()
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+        globalSettings.userId = UserHandle.USER_ALL
         whenever(telephonyManager.subscriptionId).thenReturn(SUB_1_ID)
 
         underTest =
             MobileConnectionRepositoryImpl(
+                context,
                 SUB_1_ID,
                 telephonyManager,
+                globalSettings,
+                connectionsRepo.defaultDataSubId,
+                connectionsRepo.globalMobileDataSettingChangedEvent,
                 IMMEDIATE,
                 logger,
                 scope,
@@ -148,16 +161,61 @@
         }
 
     @Test
-    fun testFlowForSubId_dataConnectionState() =
+    fun testFlowForSubId_dataConnectionState_connected() =
         runBlocking(IMMEDIATE) {
             var latest: MobileSubscriptionModel? = null
             val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
 
             val callback =
                 getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
-            callback.onDataConnectionStateChanged(100, 200 /* unused */)
+            callback.onDataConnectionStateChanged(DATA_CONNECTED, 200 /* unused */)
 
-            assertThat(latest?.dataConnectionState).isEqualTo(100)
+            assertThat(latest?.dataConnectionState).isEqualTo(DataConnectionState.Connected)
+
+            job.cancel()
+        }
+
+    @Test
+    fun testFlowForSubId_dataConnectionState_connecting() =
+        runBlocking(IMMEDIATE) {
+            var latest: MobileSubscriptionModel? = null
+            val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+
+            val callback =
+                getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
+            callback.onDataConnectionStateChanged(DATA_CONNECTING, 200 /* unused */)
+
+            assertThat(latest?.dataConnectionState).isEqualTo(DataConnectionState.Connecting)
+
+            job.cancel()
+        }
+
+    @Test
+    fun testFlowForSubId_dataConnectionState_disconnected() =
+        runBlocking(IMMEDIATE) {
+            var latest: MobileSubscriptionModel? = null
+            val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+
+            val callback =
+                getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
+            callback.onDataConnectionStateChanged(DATA_DISCONNECTED, 200 /* unused */)
+
+            assertThat(latest?.dataConnectionState).isEqualTo(DataConnectionState.Disconnected)
+
+            job.cancel()
+        }
+
+    @Test
+    fun testFlowForSubId_dataConnectionState_disconnecting() =
+        runBlocking(IMMEDIATE) {
+            var latest: MobileSubscriptionModel? = null
+            val job = underTest.subscriptionModelFlow.onEach { latest = it }.launchIn(this)
+
+            val callback =
+                getTelephonyCallbackForType<TelephonyCallback.DataConnectionStateListener>()
+            callback.onDataConnectionStateChanged(DATA_DISCONNECTING, 200 /* unused */)
+
+            assertThat(latest?.dataConnectionState).isEqualTo(DataConnectionState.Disconnecting)
 
             job.cancel()
         }
@@ -241,6 +299,87 @@
             job.cancel()
         }
 
+    @Test
+    fun dataEnabled_initial_false() =
+        runBlocking(IMMEDIATE) {
+            whenever(telephonyManager.isDataConnectionAllowed).thenReturn(true)
+
+            assertThat(underTest.dataEnabled.value).isFalse()
+        }
+
+    @Test
+    fun dataEnabled_isEnabled_true() =
+        runBlocking(IMMEDIATE) {
+            whenever(telephonyManager.isDataConnectionAllowed).thenReturn(true)
+            val job = underTest.dataEnabled.launchIn(this)
+
+            assertThat(underTest.dataEnabled.value).isTrue()
+
+            job.cancel()
+        }
+
+    @Test
+    fun dataEnabled_isDisabled() =
+        runBlocking(IMMEDIATE) {
+            whenever(telephonyManager.isDataConnectionAllowed).thenReturn(false)
+            val job = underTest.dataEnabled.launchIn(this)
+
+            assertThat(underTest.dataEnabled.value).isFalse()
+
+            job.cancel()
+        }
+
+    @Test
+    fun isDefaultDataSubscription_isDefault() =
+        runBlocking(IMMEDIATE) {
+            connectionsRepo.setDefaultDataSubId(SUB_1_ID)
+
+            var latest: Boolean? = null
+            val job = underTest.isDefaultDataSubscription.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isTrue()
+
+            job.cancel()
+        }
+
+    @Test
+    fun isDefaultDataSubscription_isNotDefault() =
+        runBlocking(IMMEDIATE) {
+            // Our subId is SUB_1_ID
+            connectionsRepo.setDefaultDataSubId(123)
+
+            var latest: Boolean? = null
+            val job = underTest.isDefaultDataSubscription.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isFalse()
+
+            job.cancel()
+        }
+
+    @Test
+    fun isDataConnectionAllowed_subIdSettingUpdate_valueUpdated() =
+        runBlocking(IMMEDIATE) {
+            val subIdSettingName = "${Settings.Global.MOBILE_DATA}$SUB_1_ID"
+
+            var latest: Boolean? = null
+            val job = underTest.dataEnabled.onEach { latest = it }.launchIn(this)
+
+            // We don't read the setting directly, we query telephony when changes happen
+            whenever(telephonyManager.isDataConnectionAllowed).thenReturn(false)
+            globalSettings.putInt(subIdSettingName, 0)
+            assertThat(latest).isFalse()
+
+            whenever(telephonyManager.isDataConnectionAllowed).thenReturn(true)
+            globalSettings.putInt(subIdSettingName, 1)
+            assertThat(latest).isTrue()
+
+            whenever(telephonyManager.isDataConnectionAllowed).thenReturn(false)
+            globalSettings.putInt(subIdSettingName, 0)
+            assertThat(latest).isFalse()
+
+            job.cancel()
+        }
+
     private fun getTelephonyCallbacks(): List<TelephonyCallback> {
         val callbackCaptor = argumentCaptor<TelephonyCallback>()
         Mockito.verify(telephonyManager).registerTelephonyCallback(any(), callbackCaptor.capture())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryTest.kt
index 326e0d281..a953a3d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/MobileConnectionsRepositoryTest.kt
@@ -16,26 +16,33 @@
 
 package com.android.systemui.statusbar.pipeline.mobile.data.repository
 
+import android.content.Intent
+import android.net.ConnectivityManager
+import android.net.Network
+import android.net.NetworkCapabilities
+import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED
+import android.net.NetworkCapabilities.TRANSPORT_CELLULAR
+import android.provider.Settings
 import android.telephony.SubscriptionInfo
 import android.telephony.SubscriptionManager
 import android.telephony.TelephonyCallback
 import android.telephony.TelephonyCallback.ActiveDataSubscriptionIdListener
 import android.telephony.TelephonyManager
 import androidx.test.filters.SmallTest
+import com.android.internal.telephony.PhoneConstants
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.mock
-import com.android.systemui.util.mockito.nullable
 import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.settings.FakeSettings
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.cancel
-import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.runBlocking
@@ -43,7 +50,6 @@
 import org.junit.Assert.assertThrows
 import org.junit.Before
 import org.junit.Test
-import org.mockito.ArgumentMatchers
 import org.mockito.Mock
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
@@ -54,32 +60,26 @@
 class MobileConnectionsRepositoryTest : SysuiTestCase() {
     private lateinit var underTest: MobileConnectionsRepositoryImpl
 
+    @Mock private lateinit var connectivityManager: ConnectivityManager
     @Mock private lateinit var subscriptionManager: SubscriptionManager
     @Mock private lateinit var telephonyManager: TelephonyManager
     @Mock private lateinit var logger: ConnectivityPipelineLogger
-    @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
 
     private val scope = CoroutineScope(IMMEDIATE)
+    private val globalSettings = FakeSettings()
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        whenever(
-                broadcastDispatcher.broadcastFlow(
-                    any(),
-                    nullable(),
-                    ArgumentMatchers.anyInt(),
-                    nullable(),
-                )
-            )
-            .thenReturn(flowOf(Unit))
 
         underTest =
             MobileConnectionsRepositoryImpl(
+                connectivityManager,
                 subscriptionManager,
                 telephonyManager,
                 logger,
-                broadcastDispatcher,
+                fakeBroadcastDispatcher,
+                globalSettings,
                 context,
                 IMMEDIATE,
                 scope,
@@ -214,6 +214,139 @@
             job.cancel()
         }
 
+    @Test
+    fun testDefaultDataSubId_updatesOnBroadcast() =
+        runBlocking(IMMEDIATE) {
+            var latest: Int? = null
+            val job = underTest.defaultDataSubId.onEach { latest = it }.launchIn(this)
+
+            fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
+                receiver.onReceive(
+                    context,
+                    Intent(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
+                        .putExtra(PhoneConstants.SUBSCRIPTION_KEY, SUB_2_ID)
+                )
+            }
+
+            assertThat(latest).isEqualTo(SUB_2_ID)
+
+            fakeBroadcastDispatcher.registeredReceivers.forEach { receiver ->
+                receiver.onReceive(
+                    context,
+                    Intent(TelephonyManager.ACTION_DEFAULT_DATA_SUBSCRIPTION_CHANGED)
+                        .putExtra(PhoneConstants.SUBSCRIPTION_KEY, SUB_1_ID)
+                )
+            }
+
+            assertThat(latest).isEqualTo(SUB_1_ID)
+
+            job.cancel()
+        }
+
+    @Test
+    fun mobileConnectivity_default() {
+        assertThat(underTest.defaultMobileNetworkConnectivity.value)
+            .isEqualTo(MobileConnectivityModel(isConnected = false, isValidated = false))
+    }
+
+    @Test
+    fun mobileConnectivity_isConnected_isValidated() =
+        runBlocking(IMMEDIATE) {
+            val caps = createCapabilities(connected = true, validated = true)
+
+            var latest: MobileConnectivityModel? = null
+            val job =
+                underTest.defaultMobileNetworkConnectivity.onEach { latest = it }.launchIn(this)
+
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+
+            assertThat(latest)
+                .isEqualTo(MobileConnectivityModel(isConnected = true, isValidated = true))
+
+            job.cancel()
+        }
+
+    @Test
+    fun globalMobileDataSettingsChangedEvent_producesOnSettingChange() =
+        runBlocking(IMMEDIATE) {
+            var produced = false
+            val job =
+                underTest.globalMobileDataSettingChangedEvent
+                    .onEach { produced = true }
+                    .launchIn(this)
+
+            assertThat(produced).isFalse()
+
+            globalSettings.putInt(Settings.Global.MOBILE_DATA, 0)
+
+            assertThat(produced).isTrue()
+
+            job.cancel()
+        }
+
+    @Test
+    fun mobileConnectivity_isConnected_isNotValidated() =
+        runBlocking(IMMEDIATE) {
+            val caps = createCapabilities(connected = true, validated = false)
+
+            var latest: MobileConnectivityModel? = null
+            val job =
+                underTest.defaultMobileNetworkConnectivity.onEach { latest = it }.launchIn(this)
+
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+
+            assertThat(latest)
+                .isEqualTo(MobileConnectivityModel(isConnected = true, isValidated = false))
+
+            job.cancel()
+        }
+
+    @Test
+    fun mobileConnectivity_isNotConnected_isNotValidated() =
+        runBlocking(IMMEDIATE) {
+            val caps = createCapabilities(connected = false, validated = false)
+
+            var latest: MobileConnectivityModel? = null
+            val job =
+                underTest.defaultMobileNetworkConnectivity.onEach { latest = it }.launchIn(this)
+
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+
+            assertThat(latest)
+                .isEqualTo(MobileConnectivityModel(isConnected = false, isValidated = false))
+
+            job.cancel()
+        }
+
+    /** In practice, I don't think this state can ever happen (!connected, validated) */
+    @Test
+    fun mobileConnectivity_isNotConnected_isValidated() =
+        runBlocking(IMMEDIATE) {
+            val caps = createCapabilities(connected = false, validated = true)
+
+            var latest: MobileConnectivityModel? = null
+            val job =
+                underTest.defaultMobileNetworkConnectivity.onEach { latest = it }.launchIn(this)
+
+            getDefaultNetworkCallback().onCapabilitiesChanged(NETWORK, caps)
+
+            assertThat(latest).isEqualTo(MobileConnectivityModel(false, true))
+
+            job.cancel()
+        }
+
+    private fun createCapabilities(connected: Boolean, validated: Boolean): NetworkCapabilities =
+        mock<NetworkCapabilities>().also {
+            whenever(it.hasTransport(TRANSPORT_CELLULAR)).thenReturn(connected)
+            whenever(it.hasCapability(NET_CAPABILITY_VALIDATED)).thenReturn(validated)
+        }
+
+    private fun getDefaultNetworkCallback(): ConnectivityManager.NetworkCallback {
+        val callbackCaptor = argumentCaptor<ConnectivityManager.NetworkCallback>()
+        verify(connectivityManager).registerDefaultNetworkCallback(callbackCaptor.capture())
+        return callbackCaptor.value!!
+    }
+
     private fun getSubscriptionCallback(): SubscriptionManager.OnSubscriptionsChangedListener {
         val callbackCaptor = argumentCaptor<SubscriptionManager.OnSubscriptionsChangedListener>()
         verify(subscriptionManager)
@@ -242,5 +375,8 @@
         private const val SUB_2_ID = 2
         private val SUB_2 =
             mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
+
+        private const val NET_ID = 123
+        private val NETWORK = mock<Network>().apply { whenever(getNetId()).thenReturn(NET_ID) }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
index cd4dbeb..3ae7d3c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconInteractor.kt
@@ -22,21 +22,29 @@
 import kotlinx.coroutines.flow.MutableStateFlow
 
 class FakeMobileIconInteractor : MobileIconInteractor {
-    private val _iconGroup = MutableStateFlow<SignalIcon.MobileIconGroup>(TelephonyIcons.UNKNOWN)
+    private val _iconGroup = MutableStateFlow<SignalIcon.MobileIconGroup>(TelephonyIcons.THREE_G)
     override val networkTypeIconGroup = _iconGroup
 
-    private val _isEmergencyOnly = MutableStateFlow<Boolean>(false)
+    private val _isEmergencyOnly = MutableStateFlow(false)
     override val isEmergencyOnly = _isEmergencyOnly
 
-    private val _level = MutableStateFlow<Int>(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
+    private val _isFailedConnection = MutableStateFlow(false)
+    override val isDefaultConnectionFailed = _isFailedConnection
+
+    override val isDataConnected = MutableStateFlow(true)
+
+    private val _isDataEnabled = MutableStateFlow(true)
+    override val isDataEnabled = _isDataEnabled
+
+    private val _isDefaultDataEnabled = MutableStateFlow(true)
+    override val isDefaultDataEnabled = _isDefaultDataEnabled
+
+    private val _level = MutableStateFlow(CellSignalStrength.SIGNAL_STRENGTH_NONE_OR_UNKNOWN)
     override val level = _level
 
-    private val _numberOfLevels = MutableStateFlow<Int>(4)
+    private val _numberOfLevels = MutableStateFlow(4)
     override val numberOfLevels = _numberOfLevels
 
-    private val _cutOut = MutableStateFlow<Boolean>(false)
-    override val cutOut = _cutOut
-
     fun setIconGroup(group: SignalIcon.MobileIconGroup) {
         _iconGroup.value = group
     }
@@ -45,6 +53,18 @@
         _isEmergencyOnly.value = emergency
     }
 
+    fun setIsDataEnabled(enabled: Boolean) {
+        _isDataEnabled.value = enabled
+    }
+
+    fun setIsDefaultDataEnabled(disabled: Boolean) {
+        _isDefaultDataEnabled.value = disabled
+    }
+
+    fun setIsFailedConnection(failed: Boolean) {
+        _isFailedConnection.value = failed
+    }
+
     fun setLevel(level: Int) {
         _level.value = level
     }
@@ -52,8 +72,4 @@
     fun setNumberOfLevels(num: Int) {
         _numberOfLevels.value = num
     }
-
-    fun setCutOut(cutOut: Boolean) {
-        _cutOut.value = cutOut
-    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
index 2bd2286..061c3b54 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/FakeMobileIconsInteractor.kt
@@ -26,8 +26,7 @@
 import com.android.systemui.statusbar.pipeline.mobile.util.MobileMappingsProxy
 import kotlinx.coroutines.flow.MutableStateFlow
 
-class FakeMobileIconsInteractor(private val mobileMappings: MobileMappingsProxy) :
-    MobileIconsInteractor {
+class FakeMobileIconsInteractor(mobileMappings: MobileMappingsProxy) : MobileIconsInteractor {
     val THREE_G_KEY = mobileMappings.toIconKey(THREE_G)
     val LTE_KEY = mobileMappings.toIconKey(LTE)
     val FOUR_G_KEY = mobileMappings.toIconKey(FOUR_G)
@@ -46,9 +45,14 @@
             FIVE_G_OVERRIDE_KEY to TelephonyIcons.NR_5G,
         )
 
+    override val isDefaultConnectionFailed = MutableStateFlow(false)
+
     private val _filteredSubscriptions = MutableStateFlow<List<SubscriptionInfo>>(listOf())
     override val filteredSubscriptions = _filteredSubscriptions
 
+    private val _activeDataConnectionHasDataEnabled = MutableStateFlow(false)
+    override val activeDataConnectionHasDataEnabled = _activeDataConnectionHasDataEnabled
+
     private val _defaultMobileIconMapping = MutableStateFlow(TEST_MAPPING)
     override val defaultMobileIconMapping = _defaultMobileIconMapping
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
index ff44af4..7fc1c0f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconInteractorTest.kt
@@ -23,6 +23,7 @@
 import com.android.settingslib.SignalIcon.MobileIconGroup
 import com.android.settingslib.mobile.TelephonyIcons
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.mobile.data.model.DataConnectionState
 import com.android.systemui.statusbar.pipeline.mobile.data.model.DefaultNetworkType
 import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileSubscriptionModel
 import com.android.systemui.statusbar.pipeline.mobile.data.model.OverrideNetworkType
@@ -34,6 +35,7 @@
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
@@ -49,12 +51,17 @@
     private val mobileIconsInteractor = FakeMobileIconsInteractor(mobileMappingsProxy)
     private val connectionRepository = FakeMobileConnectionRepository()
 
+    private val scope = CoroutineScope(IMMEDIATE)
+
     @Before
     fun setUp() {
         underTest =
             MobileIconInteractorImpl(
+                scope,
+                mobileIconsInteractor.activeDataConnectionHasDataEnabled,
                 mobileIconsInteractor.defaultMobileIconMapping,
                 mobileIconsInteractor.defaultMobileIconGroup,
+                mobileIconsInteractor.isDefaultConnectionFailed,
                 mobileMappingsProxy,
                 connectionRepository,
             )
@@ -196,6 +203,66 @@
             job.cancel()
         }
 
+    @Test
+    fun test_isDefaultDataEnabled_matchesParent() =
+        runBlocking(IMMEDIATE) {
+            var latest: Boolean? = null
+            val job = underTest.isDefaultDataEnabled.onEach { latest = it }.launchIn(this)
+
+            mobileIconsInteractor.activeDataConnectionHasDataEnabled.value = true
+            assertThat(latest).isTrue()
+
+            mobileIconsInteractor.activeDataConnectionHasDataEnabled.value = false
+            assertThat(latest).isFalse()
+
+            job.cancel()
+        }
+
+    @Test
+    fun test_isDefaultConnectionFailed_matchedParent() =
+        runBlocking(IMMEDIATE) {
+            val job = underTest.isDefaultConnectionFailed.launchIn(this)
+
+            mobileIconsInteractor.isDefaultConnectionFailed.value = false
+            assertThat(underTest.isDefaultConnectionFailed.value).isFalse()
+
+            mobileIconsInteractor.isDefaultConnectionFailed.value = true
+            assertThat(underTest.isDefaultConnectionFailed.value).isTrue()
+
+            job.cancel()
+        }
+
+    @Test
+    fun dataState_connected() =
+        runBlocking(IMMEDIATE) {
+            var latest: Boolean? = null
+            val job = underTest.isDataConnected.onEach { latest = it }.launchIn(this)
+
+            connectionRepository.setMobileSubscriptionModel(
+                MobileSubscriptionModel(dataConnectionState = DataConnectionState.Connected)
+            )
+            yield()
+
+            assertThat(latest).isTrue()
+
+            job.cancel()
+        }
+
+    @Test
+    fun dataState_notConnected() =
+        runBlocking(IMMEDIATE) {
+            var latest: Boolean? = null
+            val job = underTest.isDataConnected.onEach { latest = it }.launchIn(this)
+
+            connectionRepository.setMobileSubscriptionModel(
+                MobileSubscriptionModel(dataConnectionState = DataConnectionState.Disconnected)
+            )
+
+            assertThat(latest).isFalse()
+
+            job.cancel()
+        }
+
     companion object {
         private val IMMEDIATE = Dispatchers.Main.immediate
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
index b01efd1..b56dcd7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/domain/interactor/MobileIconsInteractorTest.kt
@@ -17,8 +17,11 @@
 package com.android.systemui.statusbar.pipeline.mobile.domain.interactor
 
 import android.telephony.SubscriptionInfo
+import android.telephony.SubscriptionManager.INVALID_SUBSCRIPTION_ID
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.pipeline.mobile.data.model.MobileConnectivityModel
+import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeMobileConnectionsRepository
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.FakeUserSetupRepository
 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
@@ -31,6 +34,7 @@
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.yield
 import org.junit.After
 import org.junit.Before
 import org.junit.Test
@@ -41,7 +45,7 @@
 class MobileIconsInteractorTest : SysuiTestCase() {
     private lateinit var underTest: MobileIconsInteractor
     private val userSetupRepository = FakeUserSetupRepository()
-    private val subscriptionsRepository = FakeMobileConnectionsRepository()
+    private val connectionsRepository = FakeMobileConnectionsRepository()
     private val mobileMappingsProxy = FakeMobileMappingsProxy()
     private val scope = CoroutineScope(IMMEDIATE)
 
@@ -50,9 +54,20 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
+
+        connectionsRepository.setMobileConnectionRepositoryMap(
+            mapOf(
+                SUB_1_ID to CONNECTION_1,
+                SUB_2_ID to CONNECTION_2,
+                SUB_3_ID to CONNECTION_3,
+                SUB_4_ID to CONNECTION_4,
+            )
+        )
+        connectionsRepository.setActiveMobileDataSubscriptionId(SUB_1_ID)
+
         underTest =
             MobileIconsInteractorImpl(
-                subscriptionsRepository,
+                connectionsRepository,
                 carrierConfigTracker,
                 mobileMappingsProxy,
                 userSetupRepository,
@@ -76,7 +91,7 @@
     @Test
     fun filteredSubscriptions_nonOpportunistic_updatesWithMultipleSubs() =
         runBlocking(IMMEDIATE) {
-            subscriptionsRepository.setSubscriptions(listOf(SUB_1, SUB_2))
+            connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_2))
 
             var latest: List<SubscriptionInfo>? = null
             val job = underTest.filteredSubscriptions.onEach { latest = it }.launchIn(this)
@@ -89,8 +104,8 @@
     @Test
     fun filteredSubscriptions_bothOpportunistic_configFalse_showsActive_3() =
         runBlocking(IMMEDIATE) {
-            subscriptionsRepository.setSubscriptions(listOf(SUB_3_OPP, SUB_4_OPP))
-            subscriptionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
+            connectionsRepository.setSubscriptions(listOf(SUB_3_OPP, SUB_4_OPP))
+            connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
             whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
                 .thenReturn(false)
 
@@ -106,8 +121,8 @@
     @Test
     fun filteredSubscriptions_bothOpportunistic_configFalse_showsActive_4() =
         runBlocking(IMMEDIATE) {
-            subscriptionsRepository.setSubscriptions(listOf(SUB_3_OPP, SUB_4_OPP))
-            subscriptionsRepository.setActiveMobileDataSubscriptionId(SUB_4_ID)
+            connectionsRepository.setSubscriptions(listOf(SUB_3_OPP, SUB_4_OPP))
+            connectionsRepository.setActiveMobileDataSubscriptionId(SUB_4_ID)
             whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
                 .thenReturn(false)
 
@@ -123,8 +138,8 @@
     @Test
     fun filteredSubscriptions_oneOpportunistic_configTrue_showsPrimary_active_1() =
         runBlocking(IMMEDIATE) {
-            subscriptionsRepository.setSubscriptions(listOf(SUB_1, SUB_3_OPP))
-            subscriptionsRepository.setActiveMobileDataSubscriptionId(SUB_1_ID)
+            connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_3_OPP))
+            connectionsRepository.setActiveMobileDataSubscriptionId(SUB_1_ID)
             whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
                 .thenReturn(true)
 
@@ -141,8 +156,8 @@
     @Test
     fun filteredSubscriptions_oneOpportunistic_configTrue_showsPrimary_nonActive_1() =
         runBlocking(IMMEDIATE) {
-            subscriptionsRepository.setSubscriptions(listOf(SUB_1, SUB_3_OPP))
-            subscriptionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
+            connectionsRepository.setSubscriptions(listOf(SUB_1, SUB_3_OPP))
+            connectionsRepository.setActiveMobileDataSubscriptionId(SUB_3_ID)
             whenever(carrierConfigTracker.alwaysShowPrimarySignalBarInOpportunisticNetworkDefault)
                 .thenReturn(true)
 
@@ -156,16 +171,104 @@
             job.cancel()
         }
 
+    @Test
+    fun activeDataConnection_turnedOn() =
+        runBlocking(IMMEDIATE) {
+            CONNECTION_1.setDataEnabled(true)
+            var latest: Boolean? = null
+            val job =
+                underTest.activeDataConnectionHasDataEnabled.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isTrue()
+
+            job.cancel()
+        }
+
+    @Test
+    fun activeDataConnection_turnedOff() =
+        runBlocking(IMMEDIATE) {
+            CONNECTION_1.setDataEnabled(true)
+            var latest: Boolean? = null
+            val job =
+                underTest.activeDataConnectionHasDataEnabled.onEach { latest = it }.launchIn(this)
+
+            CONNECTION_1.setDataEnabled(false)
+            yield()
+
+            assertThat(latest).isFalse()
+
+            job.cancel()
+        }
+
+    @Test
+    fun activeDataConnection_invalidSubId() =
+        runBlocking(IMMEDIATE) {
+            var latest: Boolean? = null
+            val job =
+                underTest.activeDataConnectionHasDataEnabled.onEach { latest = it }.launchIn(this)
+
+            connectionsRepository.setActiveMobileDataSubscriptionId(INVALID_SUBSCRIPTION_ID)
+            yield()
+
+            // An invalid active subId should tell us that data is off
+            assertThat(latest).isFalse()
+
+            job.cancel()
+        }
+
+    @Test
+    fun failedConnection_connected_validated_notFailed() =
+        runBlocking(IMMEDIATE) {
+            var latest: Boolean? = null
+            val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this)
+            connectionsRepository.setMobileConnectivity(MobileConnectivityModel(true, true))
+            yield()
+
+            assertThat(latest).isFalse()
+
+            job.cancel()
+        }
+
+    @Test
+    fun failedConnection_notConnected_notValidated_notFailed() =
+        runBlocking(IMMEDIATE) {
+            var latest: Boolean? = null
+            val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this)
+
+            connectionsRepository.setMobileConnectivity(MobileConnectivityModel(false, false))
+            yield()
+
+            assertThat(latest).isFalse()
+
+            job.cancel()
+        }
+
+    @Test
+    fun failedConnection_connected_notValidated_failed() =
+        runBlocking(IMMEDIATE) {
+            var latest: Boolean? = null
+            val job = underTest.isDefaultConnectionFailed.onEach { latest = it }.launchIn(this)
+
+            connectionsRepository.setMobileConnectivity(MobileConnectivityModel(true, false))
+            yield()
+
+            assertThat(latest).isTrue()
+
+            job.cancel()
+        }
+
     companion object {
         private val IMMEDIATE = Dispatchers.Main.immediate
 
         private const val SUB_1_ID = 1
         private val SUB_1 =
             mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_1_ID) }
+        private val CONNECTION_1 = FakeMobileConnectionRepository()
 
         private const val SUB_2_ID = 2
         private val SUB_2 =
             mock<SubscriptionInfo>().also { whenever(it.subscriptionId).thenReturn(SUB_2_ID) }
+        private val CONNECTION_2 = FakeMobileConnectionRepository()
 
         private const val SUB_3_ID = 3
         private val SUB_3_OPP =
@@ -173,6 +276,7 @@
                 whenever(it.subscriptionId).thenReturn(SUB_3_ID)
                 whenever(it.isOpportunistic).thenReturn(true)
             }
+        private val CONNECTION_3 = FakeMobileConnectionRepository()
 
         private const val SUB_4_ID = 4
         private val SUB_4_OPP =
@@ -180,5 +284,6 @@
                 whenever(it.subscriptionId).thenReturn(SUB_4_ID)
                 whenever(it.isOpportunistic).thenReturn(true)
             }
+        private val CONNECTION_4 = FakeMobileConnectionRepository()
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
index b374abb..d4c2c3f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/ui/viewmodel/MobileIconViewModelTest.kt
@@ -18,8 +18,10 @@
 
 import androidx.test.filters.SmallTest
 import com.android.settingslib.graph.SignalDrawable
-import com.android.settingslib.mobile.TelephonyIcons
+import com.android.settingslib.mobile.TelephonyIcons.THREE_G
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.statusbar.pipeline.mobile.domain.interactor.FakeMobileIconInteractor
 import com.android.systemui.statusbar.pipeline.shared.ConnectivityPipelineLogger
 import com.google.common.truth.Truth.assertThat
@@ -27,6 +29,7 @@
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.yield
 import org.junit.Before
 import org.junit.Test
 import org.mockito.Mock
@@ -43,10 +46,12 @@
         MockitoAnnotations.initMocks(this)
         interactor.apply {
             setLevel(1)
-            setCutOut(false)
-            setIconGroup(TelephonyIcons.THREE_G)
+            setIsDefaultDataEnabled(true)
+            setIsFailedConnection(false)
+            setIconGroup(THREE_G)
             setIsEmergencyOnly(false)
             setNumberOfLevels(4)
+            isDataConnected.value = true
         }
         underTest = MobileIconViewModel(SUB_1_ID, interactor, logger)
     }
@@ -56,12 +61,127 @@
         runBlocking(IMMEDIATE) {
             var latest: Int? = null
             val job = underTest.iconId.onEach { latest = it }.launchIn(this)
+            val expected = defaultSignal()
 
-            assertThat(latest).isEqualTo(SignalDrawable.getState(1, 4, false))
+            assertThat(latest).isEqualTo(expected)
 
             job.cancel()
         }
 
+    @Test
+    fun iconId_cutout_whenDefaultDataDisabled() =
+        runBlocking(IMMEDIATE) {
+            interactor.setIsDefaultDataEnabled(false)
+
+            var latest: Int? = null
+            val job = underTest.iconId.onEach { latest = it }.launchIn(this)
+            val expected = defaultSignal(level = 1, connected = false)
+
+            assertThat(latest).isEqualTo(expected)
+
+            job.cancel()
+        }
+
+    @Test
+    fun networkType_dataEnabled_groupIsRepresented() =
+        runBlocking(IMMEDIATE) {
+            val expected =
+                Icon.Resource(
+                    THREE_G.dataType,
+                    ContentDescription.Resource(THREE_G.dataContentDescription)
+                )
+            interactor.setIconGroup(THREE_G)
+
+            var latest: Icon? = null
+            val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isEqualTo(expected)
+
+            job.cancel()
+        }
+
+    @Test
+    fun networkType_nullWhenDisabled() =
+        runBlocking(IMMEDIATE) {
+            interactor.setIconGroup(THREE_G)
+            interactor.setIsDataEnabled(false)
+            var latest: Icon? = null
+            val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isNull()
+
+            job.cancel()
+        }
+
+    @Test
+    fun networkType_nullWhenFailedConnection() =
+        runBlocking(IMMEDIATE) {
+            interactor.setIconGroup(THREE_G)
+            interactor.setIsDataEnabled(true)
+            interactor.setIsFailedConnection(true)
+            var latest: Icon? = null
+            val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isNull()
+
+            job.cancel()
+        }
+
+    @Test
+    fun networkType_nullWhenDataDisconnects() =
+        runBlocking(IMMEDIATE) {
+            val initial =
+                Icon.Resource(
+                    THREE_G.dataType,
+                    ContentDescription.Resource(THREE_G.dataContentDescription)
+                )
+
+            interactor.setIconGroup(THREE_G)
+            var latest: Icon? = null
+            val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
+
+            interactor.setIconGroup(THREE_G)
+            assertThat(latest).isEqualTo(initial)
+
+            interactor.isDataConnected.value = false
+            yield()
+
+            assertThat(latest).isNull()
+
+            job.cancel()
+        }
+
+    @Test
+    fun networkType_null_changeToDisabled() =
+        runBlocking(IMMEDIATE) {
+            val expected =
+                Icon.Resource(
+                    THREE_G.dataType,
+                    ContentDescription.Resource(THREE_G.dataContentDescription)
+                )
+            interactor.setIconGroup(THREE_G)
+            interactor.setIsDataEnabled(true)
+            var latest: Icon? = null
+            val job = underTest.networkTypeIcon.onEach { latest = it }.launchIn(this)
+
+            assertThat(latest).isEqualTo(expected)
+
+            interactor.setIsDataEnabled(false)
+            yield()
+
+            assertThat(latest).isNull()
+
+            job.cancel()
+        }
+
+    /** Convenience constructor for these tests */
+    private fun defaultSignal(
+        level: Int = 1,
+        connected: Boolean = true,
+    ): Int {
+        return SignalDrawable.getState(level, /* numLevels */ 4, !connected)
+    }
+
     companion object {
         private val IMMEDIATE = Dispatchers.Main.immediate
         private const val SUB_1_ID = 1
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
index c584109..37457b3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/ui/view/ModernStatusBarWifiViewTest.kt
@@ -28,7 +28,6 @@
 import com.android.systemui.statusbar.StatusBarIconView.STATE_DOT
 import com.android.systemui.statusbar.StatusBarIconView.STATE_HIDDEN
 import com.android.systemui.statusbar.StatusBarIconView.STATE_ICON
-import com.android.systemui.statusbar.phone.StatusBarLocation
 import com.android.systemui.statusbar.pipeline.StatusBarPipelineFlags
 import com.android.systemui.statusbar.pipeline.airplane.data.repository.FakeAirplaneModeRepository
 import com.android.systemui.statusbar.pipeline.airplane.domain.interactor.AirplaneModeInteractor
@@ -40,6 +39,7 @@
 import com.android.systemui.statusbar.pipeline.wifi.data.repository.FakeWifiRepository
 import com.android.systemui.statusbar.pipeline.wifi.domain.interactor.WifiInteractor
 import com.android.systemui.statusbar.pipeline.wifi.shared.WifiConstants
+import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.LocationBasedWifiViewModel
 import com.android.systemui.statusbar.pipeline.wifi.ui.viewmodel.WifiViewModel
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
@@ -70,7 +70,7 @@
     private lateinit var connectivityRepository: FakeConnectivityRepository
     private lateinit var wifiRepository: FakeWifiRepository
     private lateinit var interactor: WifiInteractor
-    private lateinit var viewModel: WifiViewModel
+    private lateinit var viewModel: LocationBasedWifiViewModel
     private lateinit var scope: CoroutineScope
     private lateinit var airplaneModeViewModel: AirplaneModeViewModel
 
@@ -105,23 +105,19 @@
             scope,
             statusBarPipelineFlags,
             wifiConstants,
-        )
+        ).home
     }
 
     @Test
     fun constructAndBind_hasCorrectSlot() {
-        val view = ModernStatusBarWifiView.constructAndBind(
-            context, "slotName", viewModel, StatusBarLocation.HOME
-        )
+        val view = ModernStatusBarWifiView.constructAndBind(context, "slotName", viewModel)
 
         assertThat(view.slot).isEqualTo("slotName")
     }
 
     @Test
     fun getVisibleState_icon_returnsIcon() {
-        val view = ModernStatusBarWifiView.constructAndBind(
-            context, SLOT_NAME, viewModel, StatusBarLocation.HOME
-        )
+        val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
 
         view.setVisibleState(STATE_ICON, /* animate= */ false)
 
@@ -130,9 +126,7 @@
 
     @Test
     fun getVisibleState_dot_returnsDot() {
-        val view = ModernStatusBarWifiView.constructAndBind(
-            context, SLOT_NAME, viewModel, StatusBarLocation.HOME
-        )
+        val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
 
         view.setVisibleState(STATE_DOT, /* animate= */ false)
 
@@ -141,9 +135,7 @@
 
     @Test
     fun getVisibleState_hidden_returnsHidden() {
-        val view = ModernStatusBarWifiView.constructAndBind(
-            context, SLOT_NAME, viewModel, StatusBarLocation.HOME
-        )
+        val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
 
         view.setVisibleState(STATE_HIDDEN, /* animate= */ false)
 
@@ -155,9 +147,7 @@
 
     @Test
     fun setVisibleState_icon_iconShownDotHidden() {
-        val view = ModernStatusBarWifiView.constructAndBind(
-            context, SLOT_NAME, viewModel, StatusBarLocation.HOME
-        )
+        val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
 
         view.setVisibleState(STATE_ICON, /* animate= */ false)
 
@@ -172,9 +162,7 @@
 
     @Test
     fun setVisibleState_dot_iconHiddenDotShown() {
-        val view = ModernStatusBarWifiView.constructAndBind(
-            context, SLOT_NAME, viewModel, StatusBarLocation.HOME
-        )
+        val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
 
         view.setVisibleState(STATE_DOT, /* animate= */ false)
 
@@ -189,9 +177,7 @@
 
     @Test
     fun setVisibleState_hidden_iconAndDotHidden() {
-        val view = ModernStatusBarWifiView.constructAndBind(
-            context, SLOT_NAME, viewModel, StatusBarLocation.HOME
-        )
+        val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
 
         view.setVisibleState(STATE_HIDDEN, /* animate= */ false)
 
@@ -211,9 +197,7 @@
             WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 2)
         )
 
-        val view = ModernStatusBarWifiView.constructAndBind(
-            context, SLOT_NAME, viewModel, StatusBarLocation.HOME
-        )
+        val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
 
         ViewUtils.attachView(view)
         testableLooper.processAllMessages()
@@ -230,9 +214,7 @@
             WifiNetworkModel.Active(NETWORK_ID, isValidated = true, level = 2)
         )
 
-        val view = ModernStatusBarWifiView.constructAndBind(
-            context, SLOT_NAME, viewModel, StatusBarLocation.HOME
-        )
+        val view = ModernStatusBarWifiView.constructAndBind(context, SLOT_NAME, viewModel)
 
         ViewUtils.attachView(view)
         testableLooper.processAllMessages()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java
index 4f1fb02..26df03f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/HotspotControllerImplTest.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.policy;
 
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -25,6 +26,7 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
 import static org.mockito.Mockito.when;
 
 import android.net.TetheringManager;
@@ -36,6 +38,7 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
 
@@ -96,6 +99,9 @@
         }).when(mWifiManager).registerSoftApCallback(any(Executor.class),
                 any(WifiManager.SoftApCallback.class));
 
+        mContext.getOrCreateTestableResources()
+                .addOverride(R.bool.config_show_wifi_tethering, true);
+
         Handler handler = new Handler(mLooper.getLooper());
 
         mController = new HotspotControllerImpl(mContext, handler, handler, mDumpManager);
@@ -176,4 +182,18 @@
 
         verify(mCallback1).onHotspotAvailabilityChanged(false);
     }
+
+    @Test
+    public void testHotspotSupported_resource_false() {
+        mContext.getOrCreateTestableResources()
+                .addOverride(R.bool.config_show_wifi_tethering, false);
+
+        Handler handler = new Handler(mLooper.getLooper());
+
+        HotspotController controller =
+                new HotspotControllerImpl(mContext, handler, handler, mDumpManager);
+
+        verifyNoMoreInteractions(mTetheringManager);
+        assertFalse(controller.isHotspotSupported());
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
index b68eb88..91b5c35 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewDisplayControllerTest.kt
@@ -41,6 +41,7 @@
 import org.mockito.Mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.reset
+import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when` as whenever
 import org.mockito.MockitoAnnotations
@@ -85,10 +86,29 @@
     }
 
     @Test
-    fun displayView_viewAdded() {
-        underTest.displayView(getState())
+    fun displayView_viewAddedWithCorrectTitle() {
+        underTest.displayView(
+            ViewInfo(
+                name = "name",
+                windowTitle = "Fake Window Title",
+            )
+        )
 
-        verify(windowManager).addView(any(), any())
+        val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>()
+        verify(windowManager).addView(any(), capture(windowParamsCaptor))
+        assertThat(windowParamsCaptor.value!!.title).isEqualTo("Fake Window Title")
+    }
+
+    @Test
+    fun displayView_logged() {
+        underTest.displayView(
+            ViewInfo(
+                name = "name",
+                windowTitle = "Fake Window Title",
+            )
+        )
+
+        verify(logger).logViewAddition("Fake Window Title")
     }
 
     @Test
@@ -110,7 +130,7 @@
     }
 
     @Test
-    fun displayView_twice_viewNotAddedTwice() {
+    fun displayView_twiceWithSameWindowTitle_viewNotAddedTwice() {
         underTest.displayView(getState())
         reset(windowManager)
 
@@ -119,6 +139,32 @@
     }
 
     @Test
+    fun displayView_twiceWithDifferentWindowTitles_oldViewRemovedNewViewAdded() {
+        underTest.displayView(
+            ViewInfo(
+                name = "name",
+                windowTitle = "First Fake Window Title",
+            )
+        )
+
+        underTest.displayView(
+            ViewInfo(
+                name = "name",
+                windowTitle = "Second Fake Window Title",
+            )
+        )
+
+        val viewCaptor = argumentCaptor<View>()
+        val windowParamsCaptor = argumentCaptor<WindowManager.LayoutParams>()
+
+        verify(windowManager, times(2)).addView(capture(viewCaptor), capture(windowParamsCaptor))
+
+        assertThat(windowParamsCaptor.allValues[0].title).isEqualTo("First Fake Window Title")
+        assertThat(windowParamsCaptor.allValues[1].title).isEqualTo("Second Fake Window Title")
+        verify(windowManager).removeView(viewCaptor.allValues[0])
+    }
+
+    @Test
     fun displayView_viewDoesNotDisappearsBeforeTimeout() {
         val state = getState()
         underTest.displayView(state)
@@ -197,7 +243,7 @@
         underTest.removeView(reason)
 
         verify(windowManager).removeView(any())
-        verify(logger).logChipRemoval(reason)
+        verify(logger).logViewRemoval(reason)
     }
 
     @Test
@@ -232,8 +278,6 @@
         configurationController,
         powerManager,
         R.layout.chipbar,
-        "Window Title",
-        "WAKE_REASON",
     ) {
         var mostRecentViewInfo: ViewInfo? = null
 
@@ -250,9 +294,12 @@
         }
     }
 
-    inner class ViewInfo(val name: String) : TemporaryViewInfo {
-        override fun getTimeoutMs() = 1L
-    }
+    inner class ViewInfo(
+        val name: String,
+        override val windowTitle: String = "Window Title",
+        override val wakeReason: String = "WAKE_REASON",
+        override val timeoutMs: Int = 1
+    ) : TemporaryViewInfo()
 }
 
 private const val TIMEOUT_MS = 10000L
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt
index 13e9f60..d155050 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/TemporaryViewLoggerTest.kt
@@ -43,20 +43,21 @@
     }
 
     @Test
-    fun logChipAddition_bufferHasLog() {
-        logger.logChipAddition()
+    fun logViewAddition_bufferHasLog() {
+        logger.logViewAddition("Test Window Title")
 
         val stringWriter = StringWriter()
         buffer.dump(PrintWriter(stringWriter), tailLength = 0)
         val actualString = stringWriter.toString()
 
         assertThat(actualString).contains(TAG)
+        assertThat(actualString).contains("Test Window Title")
     }
 
     @Test
-    fun logChipRemoval_bufferHasTagAndReason() {
+    fun logViewRemoval_bufferHasTagAndReason() {
         val reason = "test reason"
-        logger.logChipRemoval(reason)
+        logger.logViewRemoval(reason)
 
         val stringWriter = StringWriter()
         buffer.dump(PrintWriter(stringWriter), tailLength = 0)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
index 9fbf159..f643973 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/ChipbarCoordinatorTest.kt
@@ -35,12 +35,12 @@
 import com.android.systemui.common.shared.model.ContentDescription.Companion.loadContentDescription
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.common.shared.model.Text
-import com.android.systemui.media.taptotransfer.common.MediaTttLogger
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.time.FakeSystemClock
 import com.android.systemui.util.view.ViewUtil
 import com.google.common.truth.Truth.assertThat
@@ -60,7 +60,7 @@
 class ChipbarCoordinatorTest : SysuiTestCase() {
     private lateinit var underTest: FakeChipbarCoordinator
 
-    @Mock private lateinit var logger: MediaTttLogger
+    @Mock private lateinit var logger: ChipbarLogger
     @Mock private lateinit var accessibilityManager: AccessibilityManager
     @Mock private lateinit var configurationController: ConfigurationController
     @Mock private lateinit var powerManager: PowerManager
@@ -105,7 +105,7 @@
         val drawable = context.getDrawable(R.drawable.ic_celebration)!!
 
         underTest.displayView(
-            ChipbarInfo(
+            createChipbarInfo(
                 Icon.Loaded(drawable, contentDescription = ContentDescription.Loaded("loadedCD")),
                 Text.Loaded("text"),
                 endItem = null,
@@ -121,7 +121,7 @@
     fun displayView_resourceIcon_correctlyRendered() {
         val contentDescription = ContentDescription.Resource(R.string.controls_error_timeout)
         underTest.displayView(
-            ChipbarInfo(
+            createChipbarInfo(
                 Icon.Resource(R.drawable.ic_cake, contentDescription),
                 Text.Loaded("text"),
                 endItem = null,
@@ -136,7 +136,7 @@
     @Test
     fun displayView_loadedText_correctlyRendered() {
         underTest.displayView(
-            ChipbarInfo(
+            createChipbarInfo(
                 Icon.Resource(R.id.check_box, null),
                 Text.Loaded("display view text here"),
                 endItem = null,
@@ -149,7 +149,7 @@
     @Test
     fun displayView_resourceText_correctlyRendered() {
         underTest.displayView(
-            ChipbarInfo(
+            createChipbarInfo(
                 Icon.Resource(R.id.check_box, null),
                 Text.Resource(R.string.screenrecord_start_error),
                 endItem = null,
@@ -163,7 +163,7 @@
     @Test
     fun displayView_endItemNull_correctlyRendered() {
         underTest.displayView(
-            ChipbarInfo(
+            createChipbarInfo(
                 Icon.Resource(R.id.check_box, null),
                 Text.Loaded("text"),
                 endItem = null,
@@ -179,7 +179,7 @@
     @Test
     fun displayView_endItemLoading_correctlyRendered() {
         underTest.displayView(
-            ChipbarInfo(
+            createChipbarInfo(
                 Icon.Resource(R.id.check_box, null),
                 Text.Loaded("text"),
                 endItem = ChipbarEndItem.Loading,
@@ -195,7 +195,7 @@
     @Test
     fun displayView_endItemError_correctlyRendered() {
         underTest.displayView(
-            ChipbarInfo(
+            createChipbarInfo(
                 Icon.Resource(R.id.check_box, null),
                 Text.Loaded("text"),
                 endItem = ChipbarEndItem.Error,
@@ -211,7 +211,7 @@
     @Test
     fun displayView_endItemButton_correctlyRendered() {
         underTest.displayView(
-            ChipbarInfo(
+            createChipbarInfo(
                 Icon.Resource(R.id.check_box, null),
                 Text.Loaded("text"),
                 endItem =
@@ -237,7 +237,7 @@
         val buttonClickListener = View.OnClickListener { isClicked = true }
 
         underTest.displayView(
-            ChipbarInfo(
+            createChipbarInfo(
                 Icon.Resource(R.id.check_box, null),
                 Text.Loaded("text"),
                 endItem =
@@ -260,7 +260,7 @@
         val buttonClickListener = View.OnClickListener { isClicked = true }
 
         underTest.displayView(
-            ChipbarInfo(
+            createChipbarInfo(
                 Icon.Resource(R.id.check_box, null),
                 Text.Loaded("text"),
                 endItem =
@@ -279,7 +279,7 @@
     @Test
     fun displayView_vibrationEffect_doubleClickEffect() {
         underTest.displayView(
-            ChipbarInfo(
+            createChipbarInfo(
                 Icon.Resource(R.id.check_box, null),
                 Text.Loaded("text"),
                 endItem = null,
@@ -296,7 +296,7 @@
         val drawable = context.getDrawable(R.drawable.ic_celebration)!!
 
         underTest.displayView(
-            ChipbarInfo(
+            createChipbarInfo(
                 Icon.Loaded(drawable, contentDescription = ContentDescription.Loaded("loadedCD")),
                 Text.Loaded("title text"),
                 endItem = ChipbarEndItem.Loading,
@@ -314,7 +314,7 @@
         // WHEN the view is updated
         val newDrawable = context.getDrawable(R.drawable.ic_cake)!!
         underTest.updateView(
-            ChipbarInfo(
+            createChipbarInfo(
                 Icon.Loaded(newDrawable, ContentDescription.Loaded("new CD")),
                 Text.Loaded("new title text"),
                 endItem = ChipbarEndItem.Error,
@@ -331,6 +331,47 @@
         assertThat(chipbarView.getEndButton().visibility).isEqualTo(View.GONE)
     }
 
+    @Test
+    fun viewUpdates_logged() {
+        val drawable = context.getDrawable(R.drawable.ic_celebration)!!
+        underTest.displayView(
+            createChipbarInfo(
+                Icon.Loaded(drawable, contentDescription = ContentDescription.Loaded("loadedCD")),
+                Text.Loaded("title text"),
+                endItem = ChipbarEndItem.Loading,
+            )
+        )
+
+        verify(logger).logViewUpdate(eq(WINDOW_TITLE), eq("title text"), any())
+
+        underTest.displayView(
+            createChipbarInfo(
+                Icon.Loaded(drawable, ContentDescription.Loaded("new CD")),
+                Text.Loaded("new title text"),
+                endItem = ChipbarEndItem.Error,
+            )
+        )
+
+        verify(logger).logViewUpdate(eq(WINDOW_TITLE), eq("new title text"), any())
+    }
+
+    private fun createChipbarInfo(
+        startIcon: Icon,
+        text: Text,
+        endItem: ChipbarEndItem?,
+        vibrationEffect: VibrationEffect? = null,
+    ): ChipbarInfo {
+        return ChipbarInfo(
+            startIcon,
+            text,
+            endItem,
+            vibrationEffect,
+            windowTitle = WINDOW_TITLE,
+            wakeReason = WAKE_REASON,
+            timeoutMs = TIMEOUT,
+        )
+    }
+
     private fun ViewGroup.getStartIconView() = this.requireViewById<ImageView>(R.id.start_icon)
 
     private fun ViewGroup.getChipText(): String =
@@ -350,3 +391,5 @@
 }
 
 private const val TIMEOUT = 10000
+private const val WINDOW_TITLE = "Test Chipbar Window Title"
+private const val WAKE_REASON = "TEST_CHIPBAR_WAKE_REASON"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt
index 17d4023..574f70e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/temporarydisplay/chipbar/FakeChipbarCoordinator.kt
@@ -22,8 +22,6 @@
 import android.view.WindowManager
 import android.view.accessibility.AccessibilityManager
 import com.android.systemui.classifier.FalsingCollector
-import com.android.systemui.media.taptotransfer.common.MediaTttLogger
-import com.android.systemui.media.taptotransfer.receiver.MediaTttReceiverLogger
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.policy.ConfigurationController
@@ -33,7 +31,7 @@
 /** A fake implementation of [ChipbarCoordinator] for testing. */
 class FakeChipbarCoordinator(
     context: Context,
-    @MediaTttReceiverLogger logger: MediaTttLogger,
+    logger: ChipbarLogger,
     windowManager: WindowManager,
     mainExecutor: DelayableExecutor,
     accessibilityManager: AccessibilityManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt
index 120bf79..fb781e8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/GuestUserInteractorTest.kt
@@ -23,6 +23,8 @@
 import android.os.UserManager
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.UiEventLogger
+import com.android.systemui.GuestResetOrExitSessionReceiver
+import com.android.systemui.GuestResumeSessionReceiver
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
 import com.android.systemui.user.data.repository.FakeUserRepository
@@ -55,6 +57,8 @@
     @Mock private lateinit var dismissDialog: () -> Unit
     @Mock private lateinit var selectUser: (Int) -> Unit
     @Mock private lateinit var switchUser: (Int) -> Unit
+    @Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver
+    @Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
 
     private lateinit var underTest: GuestUserInteractor
 
@@ -87,10 +91,18 @@
                         repository = repository,
                     ),
                 uiEventLogger = uiEventLogger,
+                resumeSessionReceiver = resumeSessionReceiver,
+                resetOrExitSessionReceiver = resetOrExitSessionReceiver,
             )
     }
 
     @Test
+    fun `registers broadcast receivers`() {
+        verify(resumeSessionReceiver).register()
+        verify(resetOrExitSessionReceiver).register()
+    }
+
+    @Test
     fun `onDeviceBootCompleted - allowed to add - create guest`() =
         runBlocking(IMMEDIATE) {
             setAllowedToAdd()
@@ -219,6 +231,7 @@
             repository.setUserInfos(listOf(NON_GUEST_USER_INFO, EPHEMERAL_GUEST_USER_INFO))
             repository.setSelectedUserInfo(EPHEMERAL_GUEST_USER_INFO)
             val targetUserId = NON_GUEST_USER_INFO.id
+            val ephemeralGuestUserHandle = UserHandle.of(EPHEMERAL_GUEST_USER_INFO.id)
 
             underTest.exit(
                 guestUserId = GUEST_USER_INFO.id,
@@ -230,7 +243,7 @@
             )
 
             verify(manager).markGuestForDeletion(EPHEMERAL_GUEST_USER_INFO.id)
-            verify(manager).removeUser(EPHEMERAL_GUEST_USER_INFO.id)
+            verify(manager).removeUserWhenPossible(ephemeralGuestUserHandle, false)
             verify(switchUser).invoke(targetUserId)
         }
 
@@ -240,6 +253,7 @@
             whenever(manager.markGuestForDeletion(anyInt())).thenReturn(true)
             repository.setSelectedUserInfo(GUEST_USER_INFO)
             val targetUserId = NON_GUEST_USER_INFO.id
+            val guestUserHandle = UserHandle.of(GUEST_USER_INFO.id)
 
             underTest.exit(
                 guestUserId = GUEST_USER_INFO.id,
@@ -251,7 +265,7 @@
             )
 
             verify(manager).markGuestForDeletion(GUEST_USER_INFO.id)
-            verify(manager).removeUser(GUEST_USER_INFO.id)
+            verify(manager).removeUserWhenPossible(guestUserHandle, false)
             verify(switchUser).invoke(targetUserId)
         }
 
@@ -296,6 +310,7 @@
             repository.setSelectedUserInfo(GUEST_USER_INFO)
 
             val targetUserId = NON_GUEST_USER_INFO.id
+            val guestUserHandle = UserHandle.of(GUEST_USER_INFO.id)
             underTest.remove(
                 guestUserId = GUEST_USER_INFO.id,
                 targetUserId = targetUserId,
@@ -305,7 +320,7 @@
             )
 
             verify(manager).markGuestForDeletion(GUEST_USER_INFO.id)
-            verify(manager).removeUser(GUEST_USER_INFO.id)
+            verify(manager).removeUserWhenPossible(guestUserHandle, false)
             verify(switchUser).invoke(targetUserId)
         }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
index 1680c36c..58f5531 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/domain/interactor/UserInteractorTest.kt
@@ -21,6 +21,8 @@
 import android.app.admin.DevicePolicyManager
 import android.os.UserManager
 import com.android.internal.logging.UiEventLogger
+import com.android.systemui.GuestResetOrExitSessionReceiver
+import com.android.systemui.GuestResumeSessionReceiver
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
@@ -48,6 +50,8 @@
     @Mock protected lateinit var devicePolicyManager: DevicePolicyManager
     @Mock protected lateinit var uiEventLogger: UiEventLogger
     @Mock protected lateinit var dialogShower: UserSwitchDialogController.DialogShower
+    @Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver
+    @Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
 
     protected lateinit var underTest: UserInteractor
 
@@ -107,6 +111,8 @@
                         devicePolicyManager = devicePolicyManager,
                         refreshUsersScheduler = refreshUsersScheduler,
                         uiEventLogger = uiEventLogger,
+                        resumeSessionReceiver = resumeSessionReceiver,
+                        resetOrExitSessionReceiver = resetOrExitSessionReceiver,
                     )
             )
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
index c12a868..116023a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/user/ui/viewmodel/UserSwitcherViewModelTest.kt
@@ -23,6 +23,8 @@
 import android.os.UserManager
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.UiEventLogger
+import com.android.systemui.GuestResetOrExitSessionReceiver
+import com.android.systemui.GuestResumeSessionReceiver
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.shared.model.Text
 import com.android.systemui.flags.FakeFeatureFlags
@@ -69,6 +71,8 @@
     @Mock private lateinit var deviceProvisionedController: DeviceProvisionedController
     @Mock private lateinit var devicePolicyManager: DevicePolicyManager
     @Mock private lateinit var uiEventLogger: UiEventLogger
+    @Mock private lateinit var resumeSessionReceiver: GuestResumeSessionReceiver
+    @Mock private lateinit var resetOrExitSessionReceiver: GuestResetOrExitSessionReceiver
 
     private lateinit var underTest: UserSwitcherViewModel
 
@@ -104,6 +108,8 @@
                 devicePolicyManager = devicePolicyManager,
                 refreshUsersScheduler = refreshUsersScheduler,
                 uiEventLogger = uiEventLogger,
+                resumeSessionReceiver = resumeSessionReceiver,
+                resetOrExitSessionReceiver = resetOrExitSessionReceiver,
             )
 
         underTest =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
index c254358..379bb28 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
@@ -26,8 +26,8 @@
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
 import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -44,6 +44,7 @@
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManagerGlobal;
 import android.os.Handler;
+import android.os.UserHandle;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.view.Display;
@@ -135,9 +136,10 @@
         when(mWallpaperBitmap.getHeight()).thenReturn(mBitmapHeight);
 
         // set up wallpaper manager
-        when(mWallpaperManager.peekBitmapDimensions()).thenReturn(
-                new Rect(0, 0, mBitmapWidth, mBitmapHeight));
-        when(mWallpaperManager.getBitmap(false)).thenReturn(mWallpaperBitmap);
+        when(mWallpaperManager.peekBitmapDimensions())
+                .thenReturn(new Rect(0, 0, mBitmapWidth, mBitmapHeight));
+        when(mWallpaperManager.getBitmapAsUser(eq(UserHandle.USER_CURRENT), anyBoolean()))
+                .thenReturn(mWallpaperBitmap);
         when(mMockContext.getSystemService(WallpaperManager.class)).thenReturn(mWallpaperManager);
 
         // set up surface
@@ -286,9 +288,6 @@
         testMinSurfaceHelper(8, 8);
         testMinSurfaceHelper(100, 2000);
         testMinSurfaceHelper(200, 1);
-        testMinSurfaceHelper(0, 1);
-        testMinSurfaceHelper(1, 0);
-        testMinSurfaceHelper(0, 0);
     }
 
     private void testMinSurfaceHelper(int bitmapWidth, int bitmapHeight) {
@@ -307,28 +306,6 @@
     }
 
     @Test
-    public void testZeroBitmap() {
-        // test that a frame is never drawn with a 0 bitmap
-        testZeroBitmapHelper(0, 1);
-        testZeroBitmapHelper(1, 0);
-        testZeroBitmapHelper(0, 0);
-    }
-
-    private void testZeroBitmapHelper(int bitmapWidth, int bitmapHeight) {
-
-        clearInvocations(mSurfaceHolder);
-        setBitmapDimensions(bitmapWidth, bitmapHeight);
-
-        ImageWallpaper imageWallpaper = createImageWallpaperCanvas();
-        ImageWallpaper.CanvasEngine engine =
-                (ImageWallpaper.CanvasEngine) imageWallpaper.onCreateEngine();
-        ImageWallpaper.CanvasEngine spyEngine = spy(engine);
-        spyEngine.onCreate(mSurfaceHolder);
-        spyEngine.onSurfaceRedrawNeeded(mSurfaceHolder);
-        verify(spyEngine, never()).drawFrameOnCanvas(any());
-    }
-
-    @Test
     public void testLoadDrawAndUnloadBitmap() {
         setBitmapDimensions(LOW_BMP_WIDTH, LOW_BMP_HEIGHT);
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 0c12680..627bd09 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -18,7 +18,9 @@
 package com.android.systemui.keyguard.data.repository
 
 import com.android.systemui.common.shared.model.Position
+import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.keyguard.shared.model.WakefulnessModel
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
@@ -48,6 +50,15 @@
     private val _statusBarState = MutableStateFlow(StatusBarState.SHADE)
     override val statusBarState: Flow<StatusBarState> = _statusBarState
 
+    private val _wakefulnessState = MutableStateFlow(WakefulnessModel.ASLEEP)
+    override val wakefulnessState: Flow<WakefulnessModel> = _wakefulnessState
+
+    private val _isBouncerShowing = MutableStateFlow(false)
+    override val isBouncerShowing: Flow<Boolean> = _isBouncerShowing
+
+    private val _biometricUnlockState = MutableStateFlow(BiometricUnlockModel.NONE)
+    override val biometricUnlockState: Flow<BiometricUnlockModel> = _biometricUnlockState
+
     override fun isKeyguardShowing(): Boolean {
         return _isKeyguardShowing.value
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
new file mode 100644
index 0000000..6c44244
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardTransitionRepository.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2022 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.keyguard.data.repository
+
+import android.annotation.FloatRange
+import com.android.systemui.keyguard.shared.model.TransitionInfo
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import java.util.UUID
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.SharedFlow
+
+/** Fake implementation of [KeyguardTransitionRepository] */
+class FakeKeyguardTransitionRepository : KeyguardTransitionRepository {
+
+    private val _transitions = MutableSharedFlow<TransitionStep>()
+    override val transitions: SharedFlow<TransitionStep> = _transitions
+
+    suspend fun sendTransitionStep(step: TransitionStep) {
+        _transitions.emit(step)
+    }
+
+    override fun startTransition(info: TransitionInfo): UUID? {
+        return null
+    }
+
+    override fun updateTransition(
+        transitionId: UUID,
+        @FloatRange(from = 0.0, to = 1.0) value: Float,
+        state: TransitionState
+    ) = Unit
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
index 23c7a61..2d6d29a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/utils/leaks/FakeStatusBarIconController.java
@@ -62,7 +62,11 @@
     }
 
     @Override
-    public void setSignalIcon(String slot, WifiIconState state) {
+    public void setWifiIcon(String slot, WifiIconState state) {
+    }
+
+    @Override
+    public void setNewWifiIcon() {
     }
 
     @Override
diff --git a/packages/VpnDialogs/res/values-es-rUS/strings.xml b/packages/VpnDialogs/res/values-es-rUS/strings.xml
index 108a24e..232b53a 100644
--- a/packages/VpnDialogs/res/values-es-rUS/strings.xml
+++ b/packages/VpnDialogs/res/values-es-rUS/strings.xml
@@ -17,7 +17,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="prompt" msgid="3183836924226407828">"Solicitud de conexión"</string>
-    <string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> quiere configurar una conexión VPN capaz de controlar el tráfico de la red. Acéptala solo si confías en la fuente. &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; aparece en la parte superior de la pantalla cuando se activa la VPN."</string>
+    <string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> quiere configurar una conexión VPN capaz de supervisar el tráfico de la red. Acéptala solo si confías en la fuente. &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; aparece en la parte superior de la pantalla cuando se activa la VPN."</string>
     <string name="warning" product="tv" msgid="5188957997628124947">"<xliff:g id="APP">%s</xliff:g> quiere configurar una conexión VPN que le permita supervisar el tráfico de red. Solo acéptala si confías en la fuente. &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; aparecerá en tu pantalla cuando se active la VPN."</string>
     <string name="legacy_title" msgid="192936250066580964">"La VPN está conectada."</string>
     <string name="session" msgid="6470628549473641030">"Sesión:"</string>
diff --git a/packages/VpnDialogs/res/values-es/strings.xml b/packages/VpnDialogs/res/values-es/strings.xml
index 9bf86f5..4e21fd09 100644
--- a/packages/VpnDialogs/res/values-es/strings.xml
+++ b/packages/VpnDialogs/res/values-es/strings.xml
@@ -17,7 +17,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="prompt" msgid="3183836924226407828">"Solicitud de conexión"</string>
-    <string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> quiere configurar una conexión VPN para controlar el tráfico de red. Solo debes aceptarla si confías en la fuente. &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; aparece en la parte superior de la pantalla cuando se active la conexión VPN."</string>
+    <string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> quiere configurar una conexión VPN para controlar el tráfico de red. Solo debes aceptarla si confías en la fuente. &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; aparece en la parte superior de la pantalla cuando la conexión VPN está activa."</string>
     <string name="warning" product="tv" msgid="5188957997628124947">"<xliff:g id="APP">%s</xliff:g> quiere configurar una conexión VPN que le permita monitorizar el tráfico de red. Acéptalo solo si confías en la fuente. &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; aparecerá en la pantalla cuando la VPN esté activa."</string>
     <string name="legacy_title" msgid="192936250066580964">"La VPN está conectada"</string>
     <string name="session" msgid="6470628549473641030">"Sesión:"</string>
diff --git a/packages/VpnDialogs/res/values-nl/strings.xml b/packages/VpnDialogs/res/values-nl/strings.xml
index 33f8a89..76f56af 100644
--- a/packages/VpnDialogs/res/values-nl/strings.xml
+++ b/packages/VpnDialogs/res/values-nl/strings.xml
@@ -17,7 +17,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="prompt" msgid="3183836924226407828">"Verbindingsverzoek"</string>
-    <string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> wil een VPN-verbinding opzetten om netwerkverkeer te controleren. Accepteer het verzoek alleen als je de bron vertrouwt. &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; wordt boven aan je scherm weergegeven wanneer VPN actief is."</string>
+    <string name="warning" msgid="809658604548412033">"<xliff:g id="APP">%s</xliff:g> wil een VPN-verbinding instellen waarmee de app het netwerkverkeer kan bijhouden. Accepteer dit alleen als je de bron vertrouwt. &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; verschijnt op je scherm als het VPN actief is."</string>
     <string name="warning" product="tv" msgid="5188957997628124947">"<xliff:g id="APP">%s</xliff:g> wil een VPN-verbinding instellen waarmee de app het netwerkverkeer kan bijhouden. Accepteer dit alleen als je de bron vertrouwt. &lt;br /&gt; &lt;br /&gt; &lt;img src=vpn_icon /&gt; verschijnt op je scherm als het VPN actief is."</string>
     <string name="legacy_title" msgid="192936250066580964">"Verbinding met VPN"</string>
     <string name="session" msgid="6470628549473641030">"Sessie:"</string>
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 47b4156..e3ae03c 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -3651,6 +3651,10 @@
             throw new IllegalArgumentException("The display " + displayId + " does not exist or is"
                     + " not tracked by accessibility.");
         }
+        if (mProxyManager.isProxyed(displayId)) {
+            throw new IllegalArgumentException("The display " + displayId + " is already being"
+                    + "proxy-ed");
+        }
 
         mProxyManager.registerProxy(client, displayId);
         return true;
diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
index 934b665..247f320 100644
--- a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
@@ -32,6 +32,7 @@
 import android.os.IBinder;
 import android.os.RemoteCallback;
 import android.view.KeyEvent;
+import android.view.accessibility.AccessibilityDisplayProxy;
 import android.view.accessibility.AccessibilityNodeInfo;
 
 import androidx.annotation.Nullable;
@@ -44,7 +45,7 @@
 import java.util.Set;
 
 /**
- * Represents the system connection to an {@link android.view.accessibility.AccessibilityProxy}.
+ * Represents the system connection to an {@link AccessibilityDisplayProxy}.
  *
  * <p>Most methods are no-ops since this connection does not need to capture input or listen to
  * hardware-related changes.
diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
index fb0b8f3..a2ce610 100644
--- a/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/ProxyManager.java
@@ -16,6 +16,8 @@
 package com.android.server.accessibility;
 import android.accessibilityservice.IAccessibilityServiceClient;
 
+import java.util.HashSet;
+
 /**
  * Manages proxy connections.
  *
@@ -26,6 +28,7 @@
  */
 public class ProxyManager {
     private final Object mLock;
+    private final HashSet<Integer> mDisplayIds = new HashSet<>();
 
     ProxyManager(Object lock) {
         mLock = lock;
@@ -35,12 +38,21 @@
      * TODO: Create the proxy service connection.
      */
     public void registerProxy(IAccessibilityServiceClient client, int displayId) {
+        mDisplayIds.add(displayId);
     }
 
     /**
      * TODO: Unregister the proxy service connection based on display id.
      */
     public boolean unregisterProxy(int displayId) {
+        mDisplayIds.remove(displayId);
         return true;
     }
+
+    /**
+     * Checks if a display id is being proxy-ed.
+     */
+    public boolean isProxyed(int displayId) {
+        return mDisplayIds.contains(displayId);
+    }
 }
diff --git a/services/art-profile b/services/art-profile
index 3e05078..b6398c0 100644
--- a/services/art-profile
+++ b/services/art-profile
@@ -41551,7 +41551,7 @@
 HPLcom/android/server/statusbar/StatusBarManagerService$1;->setNavigationBarLumaSamplingEnabled(IZ)V
 HSPLcom/android/server/statusbar/StatusBarManagerService$1;->setNotificationDelegate(Lcom/android/server/notification/NotificationDelegate;)V
 HPLcom/android/server/statusbar/StatusBarManagerService$1;->setTopAppHidesStatusBar(Z)V+]Lcom/android/internal/statusbar/IStatusBar;Lcom/android/internal/statusbar/IStatusBar$Stub$Proxy;
-PLcom/android/server/statusbar/StatusBarManagerService$1;->setUdfpsHbmListener(Landroid/hardware/fingerprint/IUdfpsHbmListener;)V
+PLcom/android/server/statusbar/StatusBarManagerService$1;->setUdfpsRefreshRateCallback(Landroid/hardware/fingerprint/IUdfpsRefreshRate;)V
 HPLcom/android/server/statusbar/StatusBarManagerService$1;->setWindowState(III)V
 PLcom/android/server/statusbar/StatusBarManagerService$1;->showChargingAnimation(I)V
 PLcom/android/server/statusbar/StatusBarManagerService$1;->showRecentApps(Z)V
@@ -41677,6 +41677,11 @@
 PLcom/android/server/statusbar/StatusBarManagerService;->setIcon(Ljava/lang/String;Ljava/lang/String;IILjava/lang/String;)V
 HSPLcom/android/server/statusbar/StatusBarManagerService;->setIconVisibility(Ljava/lang/String;Z)V
 HPLcom/android/server/statusbar/StatusBarManagerService;->setImeWindowStatus(ILandroid/os/IBinder;IIZ)V
+PLcom/android/server/statusbar/StatusBarManagerService;->setUdfpsRefreshRateCallback(Landroid/hardware/fingerprint/IUdfpsRefreshRate;)V
+PLcom/android/server/statusbar/StatusBarManagerService;->setUdfpsRefreshRateCallback(Landroid/hardware/fingerprint/IUdfpsRefreshRate;)V
+HSPLcom/android/server/statusbar/StatusBarManagerService;->setIconVisibility(Ljava/lang/String;Z)V+]Landroid/util/ArrayMap;Landroid/util/ArrayMap;]Lcom/android/server/statusbar/StatusBarManagerService;Lcom/android/server/statusbar/StatusBarManagerService;
+HPLcom/android/server/statusbar/StatusBarManagerService;->setImeWindowStatus(ILandroid/os/IBinder;IIZ)V+]Landroid/os/Handler;Landroid/os/Handler;]Lcom/android/server/statusbar/StatusBarManagerService;Lcom/android/server/statusbar/StatusBarManagerService;
+PLcom/android/server/statusbar/StatusBarManagerService;->setNavBarMode(I)V
 PLcom/android/server/statusbar/StatusBarManagerService;->setUdfpsHbmListener(Landroid/hardware/fingerprint/IUdfpsHbmListener;)V
 PLcom/android/server/statusbar/StatusBarManagerService;->showAuthenticationDialog(Landroid/hardware/biometrics/PromptInfo;Landroid/hardware/biometrics/IBiometricSysuiReceiver;[IZZIJLjava/lang/String;JI)V
 PLcom/android/server/statusbar/StatusBarManagerService;->suppressAmbientDisplay(Z)V
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 47ce592..64b7688 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -24,6 +24,7 @@
 import static android.service.autofill.FillEventHistory.Event.UI_TYPE_UNKNOWN;
 import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
 import static android.service.autofill.FillRequest.FLAG_PASSWORD_INPUT_TYPE;
+import static android.service.autofill.FillRequest.FLAG_RESET_FILL_DIALOG_STATE;
 import static android.service.autofill.FillRequest.FLAG_SUPPORTS_FILL_DIALOG;
 import static android.service.autofill.FillRequest.FLAG_VIEW_NOT_FOCUSED;
 import static android.service.autofill.FillRequest.INVALID_REQUEST_ID;
@@ -416,6 +417,14 @@
     @GuardedBy("mLock")
     private boolean mPreviouslyFillDialogPotentiallyStarted;
 
+    /**
+     * Keeps the fill dialog trigger ids of the last response. This invalidates
+     * the trigger ids of the previous response.
+     */
+    @Nullable
+    @GuardedBy("mLock")
+    private AutofillId[] mLastFillDialogTriggerIds;
+
     void onSwitchInputMethodLocked() {
         // One caveat is that for the case where the focus is on a field for which regular autofill
         // returns null, and augmented autofill is triggered,  and then the user switches the input
@@ -1222,6 +1231,8 @@
                 return;
             }
 
+            mLastFillDialogTriggerIds = response.getFillDialogTriggerIds();
+
             final int flags = response.getFlags();
             if ((flags & FillResponse.FLAG_DELAY_FILL) != 0) {
                 Slog.v(TAG, "Service requested to wait for delayed fill response.");
@@ -1310,6 +1321,7 @@
 
         // fallback to the default platform password manager
         mSessionFlags.mClientSuggestionsEnabled = false;
+        mLastFillDialogTriggerIds = null;
 
         final InlineSuggestionsRequest inlineRequest =
                 (mLastInlineSuggestionsRequest != null
@@ -1348,6 +1360,7 @@
                         + (timedOut ? "timeout" : "failure"));
             }
             mService.resetLastResponse();
+            mLastFillDialogTriggerIds = null;
             final LogMaker requestLog = mRequestLogs.get(requestId);
             if (requestLog == null) {
                 Slog.w(TAG, "onFillRequestFailureOrTimeout(): no log for id " + requestId);
@@ -3049,6 +3062,11 @@
             }
         }
 
+        if ((flags & FLAG_RESET_FILL_DIALOG_STATE) != 0) {
+            if (sDebug) Log.d(TAG, "force to reset fill dialog state");
+            mSessionFlags.mFillDialogDisabled = false;
+        }
+
         switch(action) {
             case ACTION_START_SESSION:
                 // View is triggering autofill.
@@ -3488,10 +3506,8 @@
     }
 
     private boolean isFillDialogUiEnabled() {
-        // TODO read from Settings or somewhere
-        final boolean isSettingsEnabledFillDialog = true;
         synchronized (mLock) {
-            return isSettingsEnabledFillDialog && !mSessionFlags.mFillDialogDisabled;
+            return !mSessionFlags.mFillDialogDisabled;
         }
     }
 
@@ -3517,14 +3533,25 @@
             AutofillId filledId, String filterText, int flags) {
         if (!isFillDialogUiEnabled()) {
             // Unsupported fill dialog UI
+            if (sDebug) Log.w(TAG, "requestShowFillDialog: fill dialog is disabled");
             return false;
         }
 
         if ((flags & FillRequest.FLAG_IME_SHOWING) != 0) {
             // IME is showing, fallback to normal suggestions UI
+            if (sDebug) Log.w(TAG, "requestShowFillDialog: IME is showing");
             return false;
         }
 
+        synchronized (mLock) {
+            if (mLastFillDialogTriggerIds == null
+                    || !ArrayUtils.contains(mLastFillDialogTriggerIds, filledId)) {
+                // Last fill dialog triggered ids are changed.
+                if (sDebug) Log.w(TAG, "Last fill dialog triggered ids are changed.");
+                return false;
+            }
+        }
+
         final Drawable serviceIcon = getServiceIcon();
 
         getUiForShowing().showFillDialog(filledId, response, filterText,
@@ -4394,6 +4421,13 @@
         if (mSessionFlags.mAugmentedAutofillOnly) {
             pw.print(prefix); pw.println("For Augmented Autofill Only");
         }
+        if (mSessionFlags.mFillDialogDisabled) {
+            pw.print(prefix); pw.println("Fill Dialog disabled");
+        }
+        if (mLastFillDialogTriggerIds != null) {
+            pw.print(prefix); pw.println("Last Fill Dialog trigger ids: ");
+            pw.println(mSelectedDatasetIds);
+        }
         if (mAugmentedAutofillDestroyer != null) {
             pw.print(prefix); pw.println("has mAugmentedAutofillDestroyer");
         }
diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index fc628cf..000bafe 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -44,6 +44,7 @@
 import android.window.DisplayWindowPolicyController;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.BlockedAppStreamingActivity;
 
 import java.util.List;
@@ -112,7 +113,9 @@
     final ArraySet<Integer> mRunningUids = new ArraySet<>();
     @Nullable private final ActivityListener mActivityListener;
     private final Handler mHandler = new Handler(Looper.getMainLooper());
-    private final ArraySet<RunningAppsChangedListener> mRunningAppsChangedListener =
+    @NonNull
+    @GuardedBy("mGenericWindowPolicyControllerLock")
+    private final ArraySet<RunningAppsChangedListener> mRunningAppsChangedListeners =
             new ArraySet<>();
     @Nullable
     private final @AssociationRequest.DeviceProfile String mDeviceProfile;
@@ -178,12 +181,16 @@
 
     /** Register a listener for running applications changes. */
     public void registerRunningAppsChangedListener(@NonNull RunningAppsChangedListener listener) {
-        mRunningAppsChangedListener.add(listener);
+        synchronized (mGenericWindowPolicyControllerLock) {
+            mRunningAppsChangedListeners.add(listener);
+        }
     }
 
     /** Unregister a listener for running applications changes. */
     public void unregisterRunningAppsChangedListener(@NonNull RunningAppsChangedListener listener) {
-        mRunningAppsChangedListener.remove(listener);
+        synchronized (mGenericWindowPolicyControllerLock) {
+            mRunningAppsChangedListeners.remove(listener);
+        }
     }
 
     @Override
@@ -283,12 +290,16 @@
                 // Post callback on the main thread so it doesn't block activity launching
                 mHandler.post(() -> mActivityListener.onDisplayEmpty(mDisplayId));
             }
-        }
-        mHandler.post(() -> {
-            for (RunningAppsChangedListener listener : mRunningAppsChangedListener) {
-                listener.onRunningAppsChanged(runningUids);
+            if (!mRunningAppsChangedListeners.isEmpty()) {
+                final ArraySet<RunningAppsChangedListener> listeners =
+                        new ArraySet<>(mRunningAppsChangedListeners);
+                mHandler.post(() -> {
+                    for (RunningAppsChangedListener listener : listeners) {
+                        listener.onRunningAppsChanged(runningUids);
+                    }
+                });
             }
-        });
+        }
     }
 
     @Override
@@ -354,4 +365,11 @@
         }
         return true;
     }
+
+    @VisibleForTesting
+    int getRunningAppsChangedListenersSizeForTesting() {
+        synchronized (mGenericWindowPolicyControllerLock) {
+            return mRunningAppsChangedListeners.size();
+        }
+    }
 }
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
index cee78e0..34787a3 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
@@ -728,7 +728,7 @@
             if (oldList != null && adding != null) {
                 adding.removeAll(oldList);
             }
-
+            addingCount = CollectionUtils.size(adding);
             EventLog.writeEvent(EventLogTags.CC_UPDATE_OPTIONS, mUserId, addingCount);
             for (int i = 0; i < addingCount; i++) {
                 String packageName = adding.valueAt(i);
diff --git a/services/core/java/android/os/BatteryStatsInternal.java b/services/core/java/android/os/BatteryStatsInternal.java
index 41044bf..b70cbe3 100644
--- a/services/core/java/android/os/BatteryStatsInternal.java
+++ b/services/core/java/android/os/BatteryStatsInternal.java
@@ -27,7 +27,6 @@
 import java.util.Collection;
 import java.util.List;
 
-
 /**
  * Battery stats local system service interface. This is used to pass internal data out of
  * BatteryStatsImpl, as well as make unchecked calls into BatteryStatsImpl.
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index fa52ac9..8055afc 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -26,9 +26,15 @@
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.InstallSourceInfo;
 import android.content.pm.ModuleInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.Signature;
+import android.content.pm.SigningDetails;
+import android.content.pm.SigningInfo;
+import android.content.pm.parsing.result.ParseResult;
+import android.content.pm.parsing.result.ParseTypeImpl;
 import android.os.Build;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -39,13 +45,19 @@
 import android.os.SystemProperties;
 import android.util.PackageUtils;
 import android.util.Slog;
+import android.util.apk.ApkSignatureVerifier;
+import android.util.apk.ApkSigningBlockUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.IBinaryTransparencyService;
 import com.android.internal.util.FrameworkStatsLog;
 
+import libcore.util.HexEncoding;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.security.PublicKey;
+import java.security.cert.CertificateException;
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
@@ -151,6 +163,117 @@
                     return 0;
                 }
 
+                private void printPackageMeasurements(PackageInfo packageInfo,
+                                                      final PrintWriter pw) {
+                    pw.print(mBinaryHashes.get(packageInfo.packageName) + ",");
+                    // TODO: To be moved to somewhere more suitable
+                    final ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
+                    ParseResult<ApkSignatureVerifier.SigningDetailsWithDigests> parseResult =
+                            ApkSignatureVerifier.verifySignaturesInternal(input,
+                                    packageInfo.applicationInfo.sourceDir,
+                                    SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3, false);
+                    if (parseResult.isError()) {
+                        pw.println("ERROR: Failed to compute package content digest for "
+                                + packageInfo.applicationInfo.sourceDir + "due to: "
+                                + parseResult.getErrorMessage());
+                    } else {
+                        ApkSignatureVerifier.SigningDetailsWithDigests signingDetails =
+                                parseResult.getResult();
+                        Map<Integer, byte[]> contentDigests = signingDetails.contentDigests;
+                        for (Map.Entry<Integer, byte[]> entry : contentDigests.entrySet()) {
+                            Integer algorithmId = entry.getKey();
+                            byte[] cDigest = entry.getValue();
+                            //pw.print("Content digest algorithm: ");
+                            switch (algorithmId) {
+                                case ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256:
+                                    pw.print("CHUNKED_SHA256:");
+                                    break;
+                                case ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512:
+                                    pw.print("CHUNKED_SHA512:");
+                                    break;
+                                case ApkSigningBlockUtils.CONTENT_DIGEST_VERITY_CHUNKED_SHA256:
+                                    pw.print("VERITY_CHUNKED_SHA256:");
+                                    break;
+                                case ApkSigningBlockUtils.CONTENT_DIGEST_SHA256:
+                                    pw.print("SHA256:");
+                                    break;
+                                default:
+                                    pw.print("UNKNOWN:");
+                            }
+                            pw.print(HexEncoding.encodeToString(cDigest, false));
+                        }
+                    }
+                }
+
+                private void printPackageInstallationInfo(PackageInfo packageInfo,
+                                                          final PrintWriter pw) {
+                    pw.println("--- Package Installation Info ---");
+                    pw.println("Current install location: "
+                            + packageInfo.applicationInfo.sourceDir);
+                    if (packageInfo.applicationInfo.sourceDir.startsWith("/data/apex/")) {
+                        String origPackageFilepath = getOriginalPreinstalledLocation(
+                                packageInfo.packageName, packageInfo.applicationInfo.sourceDir);
+                        pw.println("|--> Package pre-installed location: " + origPackageFilepath);
+                        String digest = mBinaryHashes.get(origPackageFilepath);
+                        if (digest == null) {
+                            pw.println("ERROR finding SHA256-digest from cache...");
+                        } else {
+                            pw.println("|--> Pre-installed package SHA256-digest: " + digest);
+                        }
+                    }
+                    pw.println("First install time (ms): " + packageInfo.firstInstallTime);
+                    pw.println("Last update time (ms): " + packageInfo.lastUpdateTime);
+                    boolean isPreloaded = (packageInfo.firstInstallTime
+                            == packageInfo.lastUpdateTime);
+                    pw.println("Is preloaded: " + isPreloaded);
+
+                    InstallSourceInfo installSourceInfo = getInstallSourceInfo(
+                            packageInfo.packageName);
+                    if (installSourceInfo == null) {
+                        pw.println("ERROR: Unable to obtain installSourceInfo of "
+                                + packageInfo.packageName);
+                    } else {
+                        pw.println("Installation initiated by: "
+                                + installSourceInfo.getInitiatingPackageName());
+                        pw.println("Installation done by: "
+                                + installSourceInfo.getInstallingPackageName());
+                        pw.println("Installation originating from: "
+                                + installSourceInfo.getOriginatingPackageName());
+                    }
+
+                    if (packageInfo.isApex) {
+                        pw.println("Is an active APEX: " + packageInfo.isActiveApex);
+                    }
+                }
+
+                private void printPackageSignerDetails(SigningInfo signerInfo,
+                                                       final PrintWriter pw) {
+                    if (signerInfo == null) {
+                        pw.println("ERROR: Package's signingInfo is null.");
+                        return;
+                    }
+                    pw.println("--- Package Signer Info ---");
+                    pw.println("Has multiple signers: " + signerInfo.hasMultipleSigners());
+                    Signature[] packageSigners = signerInfo.getApkContentsSigners();
+                    for (Signature packageSigner : packageSigners) {
+                        byte[] packageSignerDigestBytes =
+                                PackageUtils.computeSha256DigestBytes(packageSigner.toByteArray());
+                        String packageSignerDigestHextring =
+                                HexEncoding.encodeToString(packageSignerDigestBytes, false);
+                        pw.println("Signer cert's SHA256-digest: " + packageSignerDigestHextring);
+                        try {
+                            PublicKey publicKey = packageSigner.getPublicKey();
+                            pw.println("Signing key algorithm: " + publicKey.getAlgorithm());
+                        } catch (CertificateException e) {
+                            Slog.e(TAG,
+                                    "Failed to obtain public key of signer for cert with hash: "
+                                    + packageSignerDigestHextring);
+                            e.printStackTrace();
+                        }
+                    }
+
+                }
+
                 private void printModuleDetails(ModuleInfo moduleInfo, final PrintWriter pw) {
                     pw.println("--- Module Details ---");
                     pw.println("Module name: " + moduleInfo.getName());
@@ -190,28 +313,35 @@
                         return -1;
                     }
 
-                    pw.println("APEX Info:");
+                    if (!verbose) {
+                        pw.println("APEX Info [Format: package_name,package_version,"
+                                + "package_sha256_digest,"
+                                + "content_digest_algorithm:content_digest]:");
+                    }
                     for (PackageInfo packageInfo : getInstalledApexs()) {
+                        if (verbose) {
+                            pw.println("APEX Info [Format: package_name,package_version,"
+                                    + "package_sha256_digest,"
+                                    + "content_digest_algorithm:content_digest]:");
+                        }
                         String packageName = packageInfo.packageName;
-                        pw.println(packageName + ";"
-                                + packageInfo.getLongVersionCode() + ":"
-                                + mBinaryHashes.get(packageName).toLowerCase());
+                        pw.print(packageName + ","
+                                + packageInfo.getLongVersionCode() + ",");
+                        printPackageMeasurements(packageInfo, pw);
+                        pw.print("\n");
 
                         if (verbose) {
-                            pw.println("Install location: "
-                                    + packageInfo.applicationInfo.sourceDir);
-                            pw.println("Last Update Time (ms): " + packageInfo.lastUpdateTime);
-
                             ModuleInfo moduleInfo;
                             try {
                                 moduleInfo = pm.getModuleInfo(packageInfo.packageName, 0);
+                                pw.println("Is a module: true");
+                                printModuleDetails(moduleInfo, pw);
                             } catch (PackageManager.NameNotFoundException e) {
-                                pw.println("Is A Module: False");
-                                pw.println("");
-                                continue;
+                                pw.println("Is a module: false");
                             }
-                            pw.println("Is A Module: True");
-                            printModuleDetails(moduleInfo, pw);
+
+                            printPackageInstallationInfo(packageInfo, pw);
+                            printPackageSignerDetails(packageInfo.signingInfo, pw);
                             pw.println("");
                         }
                     }
@@ -250,25 +380,37 @@
                         return -1;
                     }
 
-                    pw.println("Module Info:");
+                    if (!verbose) {
+                        pw.println("Module Info [Format: package_name,package_version,"
+                                + "package_sha256_digest,"
+                                + "content_digest_algorithm:content_digest]:");
+                    }
                     for (ModuleInfo module : pm.getInstalledModules(PackageManager.MATCH_ALL)) {
                         String packageName = module.getPackageName();
+                        if (verbose) {
+                            pw.println("Module Info [Format: package_name,package_version,"
+                                    + "package_sha256_digest,"
+                                    + "content_digest_algorithm:content_digest]:");
+                        }
                         try {
                             PackageInfo packageInfo = pm.getPackageInfo(packageName,
-                                    PackageManager.MATCH_APEX);
-                            pw.println(packageInfo.packageName + ";"
-                                    + packageInfo.getLongVersionCode() + ":"
-                                    + mBinaryHashes.get(packageName).toLowerCase());
+                                    PackageManager.MATCH_APEX
+                                            | PackageManager.GET_SIGNING_CERTIFICATES);
+                            //pw.print("package:");
+                            pw.print(packageInfo.packageName + ",");
+                            pw.print(packageInfo.getLongVersionCode() + ",");
+                            printPackageMeasurements(packageInfo, pw);
+                            pw.print("\n");
 
                             if (verbose) {
-                                pw.println("Install location: "
-                                        + packageInfo.applicationInfo.sourceDir);
                                 printModuleDetails(module, pw);
+                                printPackageInstallationInfo(packageInfo, pw);
+                                printPackageSignerDetails(packageInfo.signingInfo, pw);
                                 pw.println("");
                             }
                         } catch (PackageManager.NameNotFoundException e) {
                             pw.println(packageName
-                                    + ";ERROR:Unable to find PackageInfo for this module.");
+                                    + ",ERROR:Unable to find PackageInfo for this module.");
                             if (verbose) {
                                 printModuleDetails(module, pw);
                                 pw.println("");
@@ -468,7 +610,8 @@
             return results;
         }
         List<PackageInfo> allPackages = pm.getInstalledPackages(
-                PackageManager.PackageInfoFlags.of(PackageManager.MATCH_APEX));
+                PackageManager.PackageInfoFlags.of(PackageManager.MATCH_APEX
+                        | PackageManager.GET_SIGNING_CERTIFICATES));
         if (allPackages == null) {
             Slog.e(TAG, "Error obtaining installed packages (including APEX)");
             return results;
@@ -478,6 +621,21 @@
         return results;
     }
 
+    @Nullable
+    private InstallSourceInfo getInstallSourceInfo(String packageName) {
+        PackageManager pm = mContext.getPackageManager();
+        if (pm == null) {
+            Slog.e(TAG, "Error obtaining an instance of PackageManager.");
+            return null;
+        }
+        try {
+            return pm.getInstallSourceInfo(packageName);
+        } catch (PackageManager.NameNotFoundException e) {
+            e.printStackTrace();
+            return null;
+        }
+    }
+
 
     /**
      * Updates the internal data structure with the most current APEX measurements.
@@ -539,6 +697,14 @@
         return true;
     }
 
+    private String getOriginalPreinstalledLocation(String packageName,
+                                                   String currentInstalledLocation) {
+        if (currentInstalledLocation.contains("/decompressed/")) {
+            return "/system/apex/" + packageName + ".capex";
+        }
+        return "/system/apex" + packageName + "apex";
+    }
+
     private void doFreshBinaryMeasurements() {
         PackageManager pm = mContext.getPackageManager();
         Slog.d(TAG, "Obtained package manager");
@@ -567,6 +733,27 @@
             mBinaryLastUpdateTimes.put(packageInfo.packageName, packageInfo.lastUpdateTime);
         }
 
+        for (PackageInfo packageInfo : getInstalledApexs()) {
+            ApplicationInfo appInfo = packageInfo.applicationInfo;
+            if (!appInfo.sourceDir.startsWith("/data/")) {
+                continue;
+            }
+            String origInstallPath = getOriginalPreinstalledLocation(packageInfo.packageName,
+                                                                     appInfo.sourceDir);
+
+            // compute SHA256 for the orig /system APEXs
+            String sha256digest = PackageUtils.computeSha256DigestForLargeFile(origInstallPath,
+                                                                               largeFileBuffer);
+            if (sha256digest == null) {
+                Slog.e(TAG, String.format("Failed to compute SHA256 digest for file at %s",
+                                          origInstallPath));
+                mBinaryHashes.put(origInstallPath, BINARY_HASH_ERROR);
+            } else {
+                mBinaryHashes.put(origInstallPath, sha256digest);
+            }
+            // there'd be no entry into mBinaryLastUpdateTimes
+        }
+
         // Next, get all installed modules from PackageManager - skip over those APEXs we've
         // processed above
         for (ModuleInfo module : pm.getInstalledModules(PackageManager.MATCH_ALL)) {
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index c1c9fbb..b56d1fc 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -231,7 +231,12 @@
             String namespaceToReset = namespaceIt.next();
             Properties properties = new Properties.Builder(namespaceToReset).build();
             try {
-                DeviceConfig.setProperties(properties);
+                if (!DeviceConfig.setProperties(properties)) {
+                    logCriticalInfo(Log.ERROR, "Failed to clear properties under "
+                            + namespaceToReset
+                            + ". Running `device_config get_sync_disabled_for_tests` will confirm"
+                            + " if config-bulk-update is enabled.");
+                }
             } catch (DeviceConfig.BadConfigException exception) {
                 logCriticalInfo(Log.WARN, "namespace " + namespaceToReset
                         + " is already banned, skip reset.");
diff --git a/services/core/java/com/android/server/SystemServerInitThreadPool.java b/services/core/java/com/android/server/SystemServerInitThreadPool.java
index 9323d95..7f24c52 100644
--- a/services/core/java/com/android/server/SystemServerInitThreadPool.java
+++ b/services/core/java/com/android/server/SystemServerInitThreadPool.java
@@ -32,6 +32,7 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
@@ -192,10 +193,12 @@
     private static void dumpStackTraces() {
         final ArrayList<Integer> pids = new ArrayList<>();
         pids.add(Process.myPid());
-        ActivityManagerService.dumpStackTraces(pids, /* processCpuTracker= */null,
-                /* lastPids= */null, Watchdog.getInterestingNativePids(),
+        ActivityManagerService.dumpStackTraces(pids,
+                /* processCpuTracker= */null, /* lastPids= */null,
+                CompletableFuture.completedFuture(Watchdog.getInterestingNativePids()),
                 /* logExceptionCreatingFile= */null, /* subject= */null,
-                /* criticalEventSection= */null, /* latencyTracker= */null);
+                /* criticalEventSection= */null, Runnable::run,
+                /* latencyTracker= */null);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/SystemServiceManager.java b/services/core/java/com/android/server/SystemServiceManager.java
index 83d86cd..953e850 100644
--- a/services/core/java/com/android/server/SystemServiceManager.java
+++ b/services/core/java/com/android/server/SystemServiceManager.java
@@ -371,6 +371,7 @@
             // 2. When a user is switched from bg to fg, the onUserVisibilityChanged() callback is
             // called onUserSwitching(), so calling it before onUserStarting() make it more
             // consistent with that
+            EventLog.writeEvent(EventLogTags.SSM_USER_VISIBILITY_CHANGED, userId, /* visible= */ 1);
             onUser(t, USER_VISIBLE, /* prevUser= */ null, targetUser);
         }
         onUser(t, USER_STARTING, /* prevUser= */ null, targetUser);
@@ -381,14 +382,30 @@
      *
      * <p><b>NOTE: </b>this method should only be called when a user that is already running become
      * visible; if the user is starting visible, callers should call
-     * {@link #onUserStarting(TimingsTraceAndSlog, int, boolean)} instead
+     * {@link #onUserStarting(TimingsTraceAndSlog, int, boolean)} instead.
      */
     public void onUserVisible(@UserIdInt int userId) {
-        EventLog.writeEvent(EventLogTags.SSM_USER_VISIBLE, userId);
+        EventLog.writeEvent(EventLogTags.SSM_USER_VISIBILITY_CHANGED, userId, /* visible= */ 1);
         onUser(USER_VISIBLE, userId);
     }
 
     /**
+     * Updates the visibility of the system user.
+     *
+     * <p>Since the system user never stops, this method must be called when it's switched from / to
+     * foreground.
+     */
+    public void onSystemUserVisibilityChanged(boolean visible) {
+        int userId = UserHandle.USER_SYSTEM;
+        EventLog.writeEvent(EventLogTags.SSM_USER_VISIBILITY_CHANGED, userId, visible ? 1 : 0);
+        if (visible) {
+            onUser(USER_VISIBLE, userId);
+        } else {
+            onUser(USER_INVISIBLE, userId);
+        }
+    }
+
+    /**
      * Unlocks the given user.
      */
     public void onUserUnlocking(@UserIdInt int userId) {
diff --git a/services/core/java/com/android/server/Watchdog.java b/services/core/java/com/android/server/Watchdog.java
index b00dec0..6eeb906 100644
--- a/services/core/java/com/android/server/Watchdog.java
+++ b/services/core/java/com/android/server/Watchdog.java
@@ -49,7 +49,7 @@
 import android.util.EventLog;
 import android.util.Log;
 import android.util.Slog;
-import android.util.SparseArray;
+import android.util.SparseBooleanArray;
 
 import com.android.internal.os.BackgroundThread;
 import com.android.internal.os.ProcessCpuTracker;
@@ -75,6 +75,7 @@
 import java.util.List;
 import java.util.Optional;
 import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -894,8 +895,9 @@
         ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(false);
         StringWriter tracesFileException = new StringWriter();
         final File stack = ActivityManagerService.dumpStackTraces(
-                pids, processCpuTracker, new SparseArray<>(), getInterestingNativePids(),
-                tracesFileException, subject, criticalEvents, /* latencyTracker= */null);
+                pids, processCpuTracker, new SparseBooleanArray(),
+                CompletableFuture.completedFuture(getInterestingNativePids()), tracesFileException,
+                subject, criticalEvents, Runnable::run, /* latencyTracker= */null);
         // Give some extra time to make sure the stack traces get written.
         // The system's been hanging for a whlie, another second or two won't hurt much.
         SystemClock.sleep(5000);
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 2cf3462..a17b5e2 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -332,6 +332,7 @@
 import android.util.PrintWriterPrinter;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.SparseBooleanArray;
 import android.util.SparseIntArray;
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
@@ -467,10 +468,15 @@
 import java.util.Objects;
 import java.util.Set;
 import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.ExecutionException;
 import java.util.concurrent.Executor;
+import java.util.concurrent.Future;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicLong;
 import java.util.function.BiFunction;
+import java.util.function.Supplier;
 
 public class ActivityManagerService extends IActivityManager.Stub
         implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback, ActivityManagerGlobalLock {
@@ -3426,14 +3432,15 @@
      * @param lastPids of dalvik VM processes to dump stack traces for last
      * @param nativePids optional list of native pids to dump stack crawls
      * @param logExceptionCreatingFile optional writer to which we log errors creating the file
+     * @param auxiliaryTaskExecutor executor to execute auxiliary tasks on
      * @param latencyTracker the latency tracker instance of the current ANR.
      */
     public static File dumpStackTraces(ArrayList<Integer> firstPids,
-            ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids,
-            ArrayList<Integer> nativePids, StringWriter logExceptionCreatingFile,
-            AnrLatencyTracker latencyTracker) {
-        return dumpStackTraces(firstPids, processCpuTracker, lastPids, nativePids,
-                logExceptionCreatingFile, null, null, null, latencyTracker);
+            ProcessCpuTracker processCpuTracker, SparseBooleanArray lastPids,
+            Future<ArrayList<Integer>> nativePidsFuture, StringWriter logExceptionCreatingFile,
+            @NonNull Executor auxiliaryTaskExecutor, AnrLatencyTracker latencyTracker) {
+        return dumpStackTraces(firstPids, processCpuTracker, lastPids, nativePidsFuture,
+                logExceptionCreatingFile, null, null, null, auxiliaryTaskExecutor, latencyTracker);
     }
 
     /**
@@ -3444,64 +3451,44 @@
      * @param logExceptionCreatingFile optional writer to which we log errors creating the file
      * @param subject optional line related to the error
      * @param criticalEventSection optional lines containing recent critical events.
+     * @param auxiliaryTaskExecutor executor to execute auxiliary tasks on
      * @param latencyTracker the latency tracker instance of the current ANR.
      */
     public static File dumpStackTraces(ArrayList<Integer> firstPids,
-            ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids,
-            ArrayList<Integer> nativePids, StringWriter logExceptionCreatingFile,
-            String subject, String criticalEventSection, AnrLatencyTracker latencyTracker) {
-        return dumpStackTraces(firstPids, processCpuTracker, lastPids, nativePids,
-                logExceptionCreatingFile, null, subject, criticalEventSection, latencyTracker);
+            ProcessCpuTracker processCpuTracker, SparseBooleanArray lastPids,
+            Future<ArrayList<Integer>> nativePidsFuture, StringWriter logExceptionCreatingFile,
+            String subject, String criticalEventSection, @NonNull Executor auxiliaryTaskExecutor,
+            AnrLatencyTracker latencyTracker) {
+        return dumpStackTraces(firstPids, processCpuTracker, lastPids, nativePidsFuture,
+                logExceptionCreatingFile, null, subject, criticalEventSection,
+                auxiliaryTaskExecutor, latencyTracker);
     }
 
     /**
-     * @param firstPidOffsets Optional, when it's set, it receives the start/end offset
+     * @param firstPidEndOffset Optional, when it's set, it receives the start/end offset
      *                        of the very first pid to be dumped.
      */
     /* package */ static File dumpStackTraces(ArrayList<Integer> firstPids,
-            ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids,
-            ArrayList<Integer> nativePids, StringWriter logExceptionCreatingFile,
-            long[] firstPidOffsets, String subject, String criticalEventSection,
-            AnrLatencyTracker latencyTracker) {
+            ProcessCpuTracker processCpuTracker, SparseBooleanArray lastPids,
+            Future<ArrayList<Integer>> nativePidsFuture, StringWriter logExceptionCreatingFile,
+            AtomicLong firstPidEndOffset, String subject, String criticalEventSection,
+            @NonNull Executor auxiliaryTaskExecutor, AnrLatencyTracker latencyTracker) {
         try {
+
             if (latencyTracker != null) {
                 latencyTracker.dumpStackTracesStarted();
             }
-            ArrayList<Integer> extraPids = null;
 
-            Slog.i(TAG, "dumpStackTraces pids=" + lastPids + " nativepids=" + nativePids);
+            Slog.i(TAG, "dumpStackTraces pids=" + lastPids);
 
             // Measure CPU usage as soon as we're called in order to get a realistic sampling
             // of the top users at the time of the request.
-            if (processCpuTracker != null) {
-                if (latencyTracker != null) {
-                    latencyTracker.processCpuTrackerMethodsCalled();
-                }
-                processCpuTracker.init();
-                try {
-                    Thread.sleep(200);
-                } catch (InterruptedException ignored) {
-                }
-
-                processCpuTracker.update();
-
-                // We'll take the stack crawls of just the top apps using CPU.
-                final int workingStatsNumber = processCpuTracker.countWorkingStats();
-                extraPids = new ArrayList<>();
-                for (int i = 0; i < workingStatsNumber && extraPids.size() < 5; i++) {
-                    ProcessCpuTracker.Stats stats = processCpuTracker.getWorkingStats(i);
-                    if (lastPids.indexOfKey(stats.pid) >= 0) {
-                        if (DEBUG_ANR) Slog.d(TAG, "Collecting stacks for extra pid " + stats.pid);
-
-                        extraPids.add(stats.pid);
-                    } else {
-                        Slog.i(TAG, "Skipping next CPU consuming process, not a java proc: "
-                                + stats.pid);
-                    }
-                }
-                if (latencyTracker != null) {
-                    latencyTracker.processCpuTrackerMethodsReturned();
-                }
+            Supplier<ArrayList<Integer>> extraPidsSupplier = processCpuTracker != null
+                    ? () -> getExtraPids(processCpuTracker, lastPids, latencyTracker) : null;
+            Future<ArrayList<Integer>> extraPidsFuture = null;
+            if (extraPidsSupplier != null) {
+                extraPidsFuture =
+                        CompletableFuture.supplyAsync(extraPidsSupplier, auxiliaryTaskExecutor);
             }
 
             final File tracesDir = new File(ANR_TRACE_DIR);
@@ -3534,15 +3521,11 @@
                         + (criticalEventSection != null ? criticalEventSection : ""));
             }
 
-            Pair<Long, Long> offsets = dumpStackTraces(
-                    tracesFile.getAbsolutePath(), firstPids, nativePids, extraPids, latencyTracker);
-            if (firstPidOffsets != null) {
-                if (offsets == null) {
-                    firstPidOffsets[0] = firstPidOffsets[1] = -1;
-                } else {
-                    firstPidOffsets[0] = offsets.first; // Start offset to the ANR trace file
-                    firstPidOffsets[1] = offsets.second; // End offset to the ANR trace file
-                }
+            long firstPidEndPos = dumpStackTraces(
+                    tracesFile.getAbsolutePath(), firstPids, nativePidsFuture,
+                    extraPidsFuture, latencyTracker);
+            if (firstPidEndOffset != null) {
+                firstPidEndOffset.set(firstPidEndPos);
             }
 
             return tracesFile;
@@ -3558,6 +3541,42 @@
     private static SimpleDateFormat sAnrFileDateFormat;
     static final String ANR_FILE_PREFIX = "anr_";
 
+    private static ArrayList<Integer> getExtraPids(ProcessCpuTracker processCpuTracker,
+            SparseBooleanArray lastPids, AnrLatencyTracker latencyTracker) {
+        if (latencyTracker != null) {
+            latencyTracker.processCpuTrackerMethodsCalled();
+        }
+        ArrayList<Integer> extraPids = new ArrayList<>();
+        processCpuTracker.init();
+        try {
+            Thread.sleep(200);
+        } catch (InterruptedException ignored) {
+        }
+
+        processCpuTracker.update();
+
+        // We'll take the stack crawls of just the top apps using CPU.
+        final int workingStatsNumber = processCpuTracker.countWorkingStats();
+        for (int i = 0; i < workingStatsNumber && extraPids.size() < 5; i++) {
+            ProcessCpuTracker.Stats stats = processCpuTracker.getWorkingStats(i);
+            if (lastPids.indexOfKey(stats.pid) >= 0) {
+                if (DEBUG_ANR) {
+                    Slog.d(TAG, "Collecting stacks for extra pid " + stats.pid);
+                }
+
+                extraPids.add(stats.pid);
+            } else {
+                Slog.i(TAG,
+                        "Skipping next CPU consuming process, not a java proc: "
+                        + stats.pid);
+            }
+        }
+        if (latencyTracker != null) {
+            latencyTracker.processCpuTrackerMethodsReturned();
+        }
+        return extraPids;
+    }
+
     private static synchronized File createAnrDumpFile(File tracesDir) throws IOException {
         if (sAnrFileDateFormat == null) {
             sAnrFileDateFormat = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss-SSS");
@@ -3661,11 +3680,11 @@
 
 
     /**
-     * @return The start/end offset of the trace of the very first PID
+     * @return The end offset of the trace of the very first PID
      */
-    public static Pair<Long, Long> dumpStackTraces(String tracesFile,
-            ArrayList<Integer> firstPids, ArrayList<Integer> nativePids,
-            ArrayList<Integer> extraPids, AnrLatencyTracker latencyTracker) {
+    public static long dumpStackTraces(String tracesFile,
+            ArrayList<Integer> firstPids, Future<ArrayList<Integer>> nativePidsFuture,
+            Future<ArrayList<Integer>> extraPidsFuture, AnrLatencyTracker latencyTracker) {
 
         Slog.i(TAG, "Dumping to " + tracesFile);
 
@@ -3679,7 +3698,6 @@
         // As applications are usually interested with the ANR stack traces, but we can't share with
         // them the stack traces other than their own stacks. So after the very first PID is
         // dumped, remember the current file size.
-        long firstPidStart = -1;
         long firstPidEnd = -1;
 
         // First collect all of the stacks of the most important pids.
@@ -3687,16 +3705,12 @@
             if (latencyTracker != null) {
                 latencyTracker.dumpingFirstPidsStarted();
             }
+
             int num = firstPids.size();
             for (int i = 0; i < num; i++) {
                 final int pid = firstPids.get(i);
                 // We don't copy ANR traces from the system_server intentionally.
                 final boolean firstPid = i == 0 && MY_PID != pid;
-                File tf = null;
-                if (firstPid) {
-                    tf = new File(tracesFile);
-                    firstPidStart = tf.exists() ? tf.length() : 0;
-                }
                 if (latencyTracker != null) {
                     latencyTracker.dumpingPidStarted(pid);
                 }
@@ -3712,11 +3726,11 @@
                 if (remainingTime <= 0) {
                     Slog.e(TAG, "Aborting stack trace dump (current firstPid=" + pid
                             + "); deadline exceeded.");
-                    return firstPidStart >= 0 ? new Pair<>(firstPidStart, firstPidEnd) : null;
+                    return firstPidEnd;
                 }
 
                 if (firstPid) {
-                    firstPidEnd = tf.length();
+                    firstPidEnd = new File(tracesFile).length();
                     // Full latency dump
                     if (latencyTracker != null) {
                         appendtoANRFile(tracesFile,
@@ -3733,6 +3747,10 @@
         }
 
         // Next collect the stacks of the native pids
+        ArrayList<Integer> nativePids = collectPids(nativePidsFuture, "native pids");
+
+        Slog.i(TAG, "dumpStackTraces nativepids=" + nativePids);
+
         if (nativePids != null) {
             if (latencyTracker != null) {
                 latencyTracker.dumpingNativePidsStarted();
@@ -3755,7 +3773,7 @@
                 if (remainingTime <= 0) {
                     Slog.e(TAG, "Aborting stack trace dump (current native pid=" + pid +
                         "); deadline exceeded.");
-                    return firstPidStart >= 0 ? new Pair<>(firstPidStart, firstPidEnd) : null;
+                    return firstPidEnd;
                 }
 
                 if (DEBUG_ANR) {
@@ -3768,6 +3786,19 @@
         }
 
         // Lastly, dump stacks for all extra PIDs from the CPU tracker.
+        ArrayList<Integer> extraPids = collectPids(extraPidsFuture, "extra pids");
+
+        if (extraPidsFuture != null) {
+            try {
+                extraPids = extraPidsFuture.get();
+            } catch (ExecutionException e) {
+                Slog.w(TAG, "Failed to collect extra pids", e.getCause());
+            } catch (InterruptedException e) {
+                Slog.w(TAG, "Interrupted while collecting extra pids", e);
+            }
+        }
+        Slog.i(TAG, "dumpStackTraces extraPids=" + extraPids);
+
         if (extraPids != null) {
             if (latencyTracker != null) {
                 latencyTracker.dumpingExtraPidsStarted();
@@ -3785,7 +3816,7 @@
                 if (remainingTime <= 0) {
                     Slog.e(TAG, "Aborting stack trace dump (current extra pid=" + pid +
                             "); deadline exceeded.");
-                    return firstPidStart >= 0 ? new Pair<>(firstPidStart, firstPidEnd) : null;
+                    return firstPidEnd;
                 }
 
                 if (DEBUG_ANR) {
@@ -3800,7 +3831,25 @@
         appendtoANRFile(tracesFile, "----- dumping ended at " + SystemClock.uptimeMillis() + "\n");
         Slog.i(TAG, "Done dumping");
 
-        return firstPidStart >= 0 ? new Pair<>(firstPidStart, firstPidEnd) : null;
+        return firstPidEnd;
+    }
+
+    private static ArrayList<Integer> collectPids(Future<ArrayList<Integer>> pidsFuture,
+            String logName) {
+
+        ArrayList<Integer> pids = null;
+
+        if (pidsFuture == null) {
+            return pids;
+        }
+        try {
+            pids = pidsFuture.get();
+        } catch (ExecutionException e) {
+            Slog.w(TAG, "Failed to collect " + logName, e.getCause());
+        } catch (InterruptedException e) {
+            Slog.w(TAG, "Interrupted while collecting " + logName , e);
+        }
+        return pids;
     }
 
     @Override
@@ -13836,6 +13885,25 @@
         }
     }
 
+    // Apply permission policy around the use of specific broadcast options
+    void enforceBroadcastOptionPermissionsInternal(@Nullable Bundle options, int callingUid) {
+        if (options != null && callingUid != Process.SYSTEM_UID) {
+            if (options.containsKey(BroadcastOptions.KEY_ALARM_BROADCAST)) {
+                if (DEBUG_BROADCAST_LIGHT) {
+                    Slog.w(TAG, "Non-system caller " + callingUid
+                            + " may not flag broadcast as alarm");
+                }
+                throw new SecurityException(
+                        "Non-system callers may not flag broadcasts as alarm");
+            }
+            if (options.containsKey(BroadcastOptions.KEY_INTERACTIVE_BROADCAST)) {
+                enforceCallingPermission(
+                        android.Manifest.permission.BROADCAST_OPTION_INTERACTIVE,
+                        "setInteractiveBroadcast");
+            }
+        }
+    }
+
     @GuardedBy("this")
     final int broadcastIntentLocked(ProcessRecord callerApp,
             String callerPackage, String callerFeatureId, Intent intent, String resolvedType,
@@ -13865,6 +13933,32 @@
             @Nullable IBinder backgroundActivityStartsToken,
             @Nullable int[] broadcastAllowList,
             @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver) {
+        final int cookie = BroadcastQueue.traceBegin("broadcastIntentLockedTraced");
+        final int res = broadcastIntentLockedTraced(callerApp, callerPackage, callerFeatureId,
+                intent, resolvedType, resultToApp, resultTo, resultCode, resultData, resultExtras,
+                requiredPermissions, excludedPermissions, excludedPackages, appOp, bOptions,
+                ordered, sticky, callingPid, callingUid, realCallingUid, realCallingPid, userId,
+                allowBackgroundActivityStarts, backgroundActivityStartsToken, broadcastAllowList,
+                filterExtrasForReceiver);
+        BroadcastQueue.traceEnd(cookie);
+        return res;
+    }
+
+    @GuardedBy("this")
+    final int broadcastIntentLockedTraced(ProcessRecord callerApp, String callerPackage,
+            @Nullable String callerFeatureId, Intent intent, String resolvedType,
+            ProcessRecord resultToApp, IIntentReceiver resultTo, int resultCode, String resultData,
+            Bundle resultExtras, String[] requiredPermissions,
+            String[] excludedPermissions, String[] excludedPackages, int appOp, Bundle bOptions,
+            boolean ordered, boolean sticky, int callingPid, int callingUid,
+            int realCallingUid, int realCallingPid, int userId,
+            boolean allowBackgroundActivityStarts,
+            @Nullable IBinder backgroundActivityStartsToken,
+            @Nullable int[] broadcastAllowList,
+            @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver) {
+        // Ensure all internal loopers are registered for idle checks
+        BroadcastLoopers.addMyLooper();
+
         if ((resultTo != null) && (resultToApp == null)) {
             if (resultTo.asBinder() instanceof BinderProxy) {
                 // Warn when requesting results without a way to deliver them
@@ -14413,6 +14507,7 @@
         }
 
         // Figure out who all will receive this broadcast.
+        final int cookie = BroadcastQueue.traceBegin("queryReceivers");
         List receivers = null;
         List<BroadcastFilter> registeredReceivers = null;
         // Need to resolve the intent to interested receivers...
@@ -14443,6 +14538,7 @@
                         resolvedType, false /*defaultOnly*/, userId);
             }
         }
+        BroadcastQueue.traceEnd(cookie);
 
         final boolean replacePending =
                 (intent.getFlags()&Intent.FLAG_RECEIVER_REPLACE_PENDING) != 0;
@@ -14627,6 +14723,15 @@
         mCurBroadcastStats.addBackgroundCheckViolation(action, targetPackage);
     }
 
+    final void notifyBroadcastFinishedLocked(@NonNull BroadcastRecord original) {
+        final ApplicationInfo info = original.callerApp != null ? original.callerApp.info : null;
+        final String callerPackage = info != null ? info.packageName : original.callerPackage;
+        if (callerPackage != null) {
+            mHandler.obtainMessage(ActivityManagerService.DISPATCH_SENDING_BROADCAST_EVENT,
+                    original.callingUid, 0, callerPackage).sendToTarget();
+        }
+    }
+
     final Intent verifyBroadcastLocked(Intent intent) {
         // Refuse possible leaked file descriptors
         if (intent != null && intent.hasFileDescriptors() == true) {
@@ -14700,19 +14805,8 @@
             // We're delivering the result to the caller
             final ProcessRecord resultToApp = callerApp;
 
-            // Non-system callers can't declare that a broadcast is alarm-related.
-            // The PendingIntent invocation case is handled in PendingIntentRecord.
-            if (bOptions != null && callingUid != SYSTEM_UID) {
-                if (bOptions.containsKey(BroadcastOptions.KEY_ALARM_BROADCAST)
-                        || bOptions.containsKey(BroadcastOptions.KEY_INTERACTIVE_BROADCAST)) {
-                    if (DEBUG_BROADCAST) {
-                        Slog.w(TAG, "Non-system caller " + callingUid
-                                + " may not flag broadcast as alarm or interactive");
-                    }
-                    throw new SecurityException(
-                            "Non-system callers may not flag broadcasts as alarm or interactive");
-                }
-            }
+            // Permission regimes around sender-supplied broadcast options.
+            enforceBroadcastOptionPermissionsInternal(bOptions, callingUid);
 
             final long origId = Binder.clearCallingIdentity();
             try {
@@ -16768,7 +16862,6 @@
             mAtmInternal.onUserStopped(userId);
             // Clean up various services by removing the user
             mBatteryStatsService.onUserRemoved(userId);
-            mUserController.onUserRemoved(userId);
         }
 
         @Override
@@ -16907,6 +17000,11 @@
             return mEnableModernQueue;
         }
 
+        @Override
+        public void enforceBroadcastOptionsPermissions(Bundle options, int callingUid) {
+            enforceBroadcastOptionPermissionsInternal(options, callingUid);
+        }
+
         /**
          * Returns package name by pid.
          */
@@ -18115,6 +18213,7 @@
 
     public void waitForBroadcastIdle(@Nullable PrintWriter pw) {
         enforceCallingPermission(permission.DUMP, "waitForBroadcastIdle()");
+        BroadcastLoopers.waitForIdle(pw);
         for (BroadcastQueue queue : mBroadcastQueues) {
             queue.waitForIdle(pw);
         }
@@ -18126,6 +18225,7 @@
 
     public void waitForBroadcastBarrier(@Nullable PrintWriter pw) {
         enforceCallingPermission(permission.DUMP, "waitForBroadcastBarrier()");
+        BroadcastLoopers.waitForIdle(pw);
         for (BroadcastQueue queue : mBroadcastQueues) {
             queue.waitForBarrier(pw);
         }
diff --git a/services/core/java/com/android/server/am/AnrHelper.java b/services/core/java/com/android/server/am/AnrHelper.java
index 6de4118..71c80ea 100644
--- a/services/core/java/com/android/server/am/AnrHelper.java
+++ b/services/core/java/com/android/server/am/AnrHelper.java
@@ -25,10 +25,15 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.TimeoutRecord;
 import com.android.server.wm.WindowProcessController;
 
 import java.util.ArrayList;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.LinkedBlockingQueue;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.ThreadPoolExecutor;
 import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicBoolean;
 
@@ -51,6 +56,14 @@
      */
     private static final long CONSECUTIVE_ANR_TIME_MS = TimeUnit.MINUTES.toMillis(2);
 
+    /**
+     * The keep alive time for the threads in the helper threadpool executor
+    */
+    private static final int AUX_THREAD_KEEP_ALIVE_SECOND = 10;
+
+    private static final ThreadFactory sDefaultThreadFactory =  r ->
+            new Thread(r, "AnrAuxiliaryTaskExecutor");
+
     @GuardedBy("mAnrRecords")
     private final ArrayList<AnrRecord> mAnrRecords = new ArrayList<>();
     private final AtomicBoolean mRunning = new AtomicBoolean(false);
@@ -66,8 +79,18 @@
     @GuardedBy("mAnrRecords")
     private int mProcessingPid = -1;
 
+    private final ExecutorService mAuxiliaryTaskExecutor;
+
     AnrHelper(final ActivityManagerService service) {
+        this(service, new ThreadPoolExecutor(/* corePoolSize= */ 0, /* maximumPoolSize= */ 1,
+                /* keepAliveTime= */ AUX_THREAD_KEEP_ALIVE_SECOND, TimeUnit.SECONDS,
+                new LinkedBlockingQueue<>(), sDefaultThreadFactory));
+    }
+
+    @VisibleForTesting
+    AnrHelper(ActivityManagerService service, ExecutorService auxExecutor) {
         mService = service;
+        mAuxiliaryTaskExecutor = auxExecutor;
     }
 
     void appNotResponding(ProcessRecord anrProcess, TimeoutRecord timeoutRecord) {
@@ -108,7 +131,8 @@
                 }
                 timeoutRecord.mLatencyTracker.anrRecordPlacingOnQueueWithSize(mAnrRecords.size());
                 mAnrRecords.add(new AnrRecord(anrProcess, activityShortComponentName, aInfo,
-                        parentShortComponentName, parentProcess, aboveSystem, timeoutRecord));
+                        parentShortComponentName, parentProcess, aboveSystem,
+                        mAuxiliaryTaskExecutor, timeoutRecord));
             }
             startAnrConsumerIfNeeded();
         } finally {
@@ -204,11 +228,12 @@
         final ApplicationInfo mAppInfo;
         final WindowProcessController mParentProcess;
         final boolean mAboveSystem;
+        final ExecutorService mAuxiliaryTaskExecutor;
         final long mTimestamp = SystemClock.uptimeMillis();
         AnrRecord(ProcessRecord anrProcess, String activityShortComponentName,
                 ApplicationInfo aInfo, String parentShortComponentName,
                 WindowProcessController parentProcess, boolean aboveSystem,
-                TimeoutRecord timeoutRecord) {
+                ExecutorService auxiliaryTaskExecutor, TimeoutRecord timeoutRecord) {
             mApp = anrProcess;
             mPid = anrProcess.mPid;
             mActivityShortComponentName = activityShortComponentName;
@@ -217,6 +242,7 @@
             mAppInfo = aInfo;
             mParentProcess = parentProcess;
             mAboveSystem = aboveSystem;
+            mAuxiliaryTaskExecutor = auxiliaryTaskExecutor;
         }
 
         void appNotResponding(boolean onlyDumpSelf) {
@@ -224,7 +250,7 @@
                 mTimeoutRecord.mLatencyTracker.anrProcessingStarted();
                 mApp.mErrorState.appNotResponding(mActivityShortComponentName, mAppInfo,
                         mParentShortComponentName, mParentProcess, mAboveSystem,
-                        mTimeoutRecord, onlyDumpSelf);
+                        mTimeoutRecord, mAuxiliaryTaskExecutor, onlyDumpSelf);
             } finally {
                 mTimeoutRecord.mLatencyTracker.anrProcessingEnded();
             }
diff --git a/services/core/java/com/android/server/am/BroadcastLoopers.java b/services/core/java/com/android/server/am/BroadcastLoopers.java
new file mode 100644
index 0000000..b828720
--- /dev/null
+++ b/services/core/java/com/android/server/am/BroadcastLoopers.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.am;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.os.Looper;
+import android.os.Message;
+import android.os.MessageQueue;
+import android.os.SystemClock;
+import android.util.ArraySet;
+import android.util.Slog;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.PrintWriter;
+import java.util.Objects;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * Collection of {@link Looper} that are known to be used for broadcast dispatch
+ * within the system. This collection can be useful for callers interested in
+ * confirming that all pending broadcasts have been successfully enqueued.
+ */
+public class BroadcastLoopers {
+    private static final String TAG = "BroadcastLoopers";
+
+    @GuardedBy("sLoopers")
+    private static final ArraySet<Looper> sLoopers = new ArraySet<>();
+
+    /**
+     * Register the given {@link Looper} as possibly having messages that will
+     * dispatch broadcasts.
+     */
+    public static void addLooper(@NonNull Looper looper) {
+        synchronized (sLoopers) {
+            sLoopers.add(Objects.requireNonNull(looper));
+        }
+    }
+
+    /**
+     * If the current thread is hosting a {@link Looper}, then register it as
+     * possibly having messages that will dispatch broadcasts.
+     */
+    public static void addMyLooper() {
+        final Looper looper = Looper.myLooper();
+        if (looper != null) {
+            synchronized (sLoopers) {
+                if (sLoopers.add(looper)) {
+                    Slog.w(TAG, "Found previously unknown looper " + looper.getThread());
+                }
+            }
+        }
+    }
+
+    /**
+     * Wait for all registered {@link Looper} instances to become idle, as
+     * defined by {@link MessageQueue#isIdle()}. Note that {@link Message#when}
+     * still in the future are ignored for the purposes of the idle test.
+     */
+    public static void waitForIdle(@Nullable PrintWriter pw) {
+        final CountDownLatch latch;
+        synchronized (sLoopers) {
+            final int N = sLoopers.size();
+            latch = new CountDownLatch(N);
+            for (int i = 0; i < N; i++) {
+                final MessageQueue queue = sLoopers.valueAt(i).getQueue();
+                if (queue.isIdle()) {
+                    latch.countDown();
+                } else {
+                    queue.addIdleHandler(() -> {
+                        latch.countDown();
+                        return false;
+                    });
+                }
+            }
+        }
+
+        long lastPrint = 0;
+        while (latch.getCount() > 0) {
+            final long now = SystemClock.uptimeMillis();
+            if (now >= lastPrint + 1000) {
+                lastPrint = now;
+                logv("Waiting for " + latch.getCount() + " loopers to drain...", pw);
+            }
+            SystemClock.sleep(100);
+        }
+        logv("Loopers drained!", pw);
+    }
+
+    private static void logv(@NonNull String msg, @Nullable PrintWriter pw) {
+        Slog.v(TAG, msg);
+        if (pw != null) {
+            pw.println(msg);
+            pw.flush();
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/BroadcastProcessQueue.java b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
index f7d24e9..c994f13 100644
--- a/services/core/java/com/android/server/am/BroadcastProcessQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastProcessQueue.java
@@ -114,7 +114,14 @@
      * dispatched to this process, in the same representation as
      * {@link #mPending}.
      */
-    private final ArrayDeque<SomeArgs> mPendingUrgent = new ArrayDeque<>();
+    private final ArrayDeque<SomeArgs> mPendingUrgent = new ArrayDeque<>(4);
+
+    /**
+     * Ordered collection of "offload" broadcasts that are waiting to be
+     * dispatched to this process, in the same representation as
+     * {@link #mPending}.
+     */
+    private final ArrayDeque<SomeArgs> mPendingOffload = new ArrayDeque<>(4);
 
     /**
      * Broadcast actively being dispatched to this process.
@@ -128,14 +135,6 @@
     private int mActiveIndex;
 
     /**
-     * When defined, the receiver actively being dispatched into this process
-     * was considered "blocked" until at least the given count of other
-     * receivers have reached a terminal state; typically used for ordered
-     * broadcasts and priority traunches.
-     */
-    private int mActiveBlockedUntilTerminalCount;
-
-    /**
      * Count of {@link #mActive} broadcasts that have been dispatched since this
      * queue was last idle.
      */
@@ -148,8 +147,7 @@
     private boolean mActiveViaColdStart;
 
     /**
-     * Count of {@link #mPending} and {@link #mPendingUrgent} broadcasts of
-     * these various flavors.
+     * Count of pending broadcasts of these various flavors.
      */
     private int mCountForeground;
     private int mCountOrdered;
@@ -177,6 +175,16 @@
         this.uid = uid;
     }
 
+    private @NonNull ArrayDeque<SomeArgs> getQueueForBroadcast(@NonNull BroadcastRecord record) {
+        if (record.isUrgent()) {
+            return mPendingUrgent;
+        } else if (record.isOffload()) {
+            return mPendingOffload;
+        } else {
+            return mPending;
+        }
+    }
+
     /**
      * Enqueue the given broadcast to be dispatched to this process at some
      * future point in time. The target receiver is indicated by the given index
@@ -190,13 +198,11 @@
      * given count of other receivers have reached a terminal state; typically
      * used for ordered broadcasts and priority traunches.
      */
-    public void enqueueOrReplaceBroadcast(@NonNull BroadcastRecord record, int recordIndex,
-            int blockedUntilTerminalCount) {
+    public void enqueueOrReplaceBroadcast(@NonNull BroadcastRecord record, int recordIndex) {
         if (record.isReplacePending()) {
-            boolean didReplace = replaceBroadcastInQueue(mPending,
-                    record, recordIndex, blockedUntilTerminalCount)
-                    || replaceBroadcastInQueue(mPendingUrgent,
-                    record, recordIndex, blockedUntilTerminalCount);
+            boolean didReplace = replaceBroadcastInQueue(mPending, record, recordIndex)
+                    || replaceBroadcastInQueue(mPendingUrgent, record, recordIndex)
+                    || replaceBroadcastInQueue(mPendingOffload, record, recordIndex);
             if (didReplace) {
                 return;
             }
@@ -207,14 +213,12 @@
         SomeArgs newBroadcastArgs = SomeArgs.obtain();
         newBroadcastArgs.arg1 = record;
         newBroadcastArgs.argi1 = recordIndex;
-        newBroadcastArgs.argi2 = blockedUntilTerminalCount;
 
         // Cross-broadcast prioritization policy:  some broadcasts might warrant being
         // issued ahead of others that are already pending, for example if this new
         // broadcast is in a different delivery class or is tied to a direct user interaction
         // with implicit responsiveness expectations.
-        final ArrayDeque<SomeArgs> queue = record.isUrgent() ? mPendingUrgent : mPending;
-        queue.addLast(newBroadcastArgs);
+        getQueueForBroadcast(record).addLast(newBroadcastArgs);
         onBroadcastEnqueued(record, recordIndex);
     }
 
@@ -227,7 +231,7 @@
      * {@code false} otherwise.
      */
     private boolean replaceBroadcastInQueue(@NonNull ArrayDeque<SomeArgs> queue,
-            @NonNull BroadcastRecord record, int recordIndex,  int blockedUntilTerminalCount) {
+            @NonNull BroadcastRecord record, int recordIndex) {
         final Iterator<SomeArgs> it = queue.descendingIterator();
         final Object receiver = record.receivers.get(recordIndex);
         while (it.hasNext()) {
@@ -242,7 +246,6 @@
                 // Exact match found; perform in-place swap
                 args.arg1 = record;
                 args.argi1 = recordIndex;
-                args.argi2 = blockedUntilTerminalCount;
                 onBroadcastDequeued(testRecord, testRecordIndex);
                 onBroadcastEnqueued(record, recordIndex);
                 return true;
@@ -279,10 +282,13 @@
      */
     public boolean forEachMatchingBroadcast(@NonNull BroadcastPredicate predicate,
             @NonNull BroadcastConsumer consumer, boolean andRemove) {
-        boolean didSomething = forEachMatchingBroadcastInQueue(mPending,
+        boolean didSomething = false;
+        didSomething |= forEachMatchingBroadcastInQueue(mPending,
                 predicate, consumer, andRemove);
         didSomething |= forEachMatchingBroadcastInQueue(mPendingUrgent,
                 predicate, consumer, andRemove);
+        didSomething |= forEachMatchingBroadcastInQueue(mPendingOffload,
+                predicate, consumer, andRemove);
         return didSomething;
     }
 
@@ -391,7 +397,6 @@
         final SomeArgs next = removeNextBroadcast();
         mActive = (BroadcastRecord) next.arg1;
         mActiveIndex = next.argi1;
-        mActiveBlockedUntilTerminalCount = next.argi2;
         mActiveCountSinceIdle++;
         mActiveViaColdStart = false;
         next.recycle();
@@ -404,7 +409,6 @@
     public void makeActiveIdle() {
         mActive = null;
         mActiveIndex = 0;
-        mActiveBlockedUntilTerminalCount = -1;
         mActiveCountSinceIdle = 0;
         mActiveViaColdStart = false;
         invalidateRunnableAt();
@@ -516,7 +520,7 @@
     }
 
     public boolean isEmpty() {
-        return mPending.isEmpty() && mPendingUrgent.isEmpty();
+        return mPending.isEmpty() && mPendingUrgent.isEmpty() && mPendingOffload.isEmpty();
     }
 
     public boolean isActive() {
@@ -537,6 +541,8 @@
             return mPendingUrgent;
         } else if (!mPending.isEmpty()) {
             return mPending;
+        } else if (!mPendingOffload.isEmpty()) {
+            return mPendingOffload;
         }
         return null;
     }
@@ -581,12 +587,15 @@
         }
         final SomeArgs next = mPending.peekFirst();
         final SomeArgs nextUrgent = mPendingUrgent.peekFirst();
+        final SomeArgs nextOffload = mPendingOffload.peekFirst();
         // Empty queue is past any barrier
-        final boolean nextLater = next == null
+        final boolean nextLater = (next == null)
                 || ((BroadcastRecord) next.arg1).enqueueTime > barrierTime;
-        final boolean nextUrgentLater = nextUrgent == null
+        final boolean nextUrgentLater = (nextUrgent == null)
                 || ((BroadcastRecord) nextUrgent.arg1).enqueueTime > barrierTime;
-        return nextLater && nextUrgentLater;
+        final boolean nextOffloadLater = (nextOffload == null)
+                || ((BroadcastRecord) nextOffload.arg1).enqueueTime > barrierTime;
+        return nextLater && nextUrgentLater && nextOffloadLater;
     }
 
     public boolean isRunnable() {
@@ -680,7 +689,7 @@
         if (next != null) {
             final BroadcastRecord r = (BroadcastRecord) next.arg1;
             final int index = next.argi1;
-            final int blockedUntilTerminalCount = next.argi2;
+            final int blockedUntilTerminalCount = r.blockedUntilTerminalCount[index];
             final long runnableAt = r.enqueueTime;
 
             // We might be blocked waiting for other receivers to finish,
@@ -726,8 +735,9 @@
 
             // If we have too many broadcasts pending, bypass any delays that
             // might have been applied above to aid draining
-            if (mPending.size() + mPendingUrgent.size() >= constants.MAX_PENDING_BROADCASTS) {
-                mRunnableAt = runnableAt;
+            if (mPending.size() + mPendingUrgent.size()
+                    + mPendingOffload.size() >= constants.MAX_PENDING_BROADCASTS) {
+                mRunnableAt = Math.min(mRunnableAt, runnableAt);
                 mRunnableAtReason = REASON_MAX_PENDING;
             }
         } else {
@@ -845,23 +855,27 @@
         pw.println();
         pw.increaseIndent();
         if (mActive != null) {
-            dumpRecord(now, pw, mActive, mActiveIndex, mActiveBlockedUntilTerminalCount);
+            dumpRecord("ACTIVE", now, pw, mActive, mActiveIndex);
         }
         for (SomeArgs args : mPendingUrgent) {
             final BroadcastRecord r = (BroadcastRecord) args.arg1;
-            dumpRecord(now, pw, r, args.argi1, args.argi2);
+            dumpRecord("URGENT", now, pw, r, args.argi1);
         }
         for (SomeArgs args : mPending) {
             final BroadcastRecord r = (BroadcastRecord) args.arg1;
-            dumpRecord(now, pw, r, args.argi1, args.argi2);
+            dumpRecord(null, now, pw, r, args.argi1);
+        }
+        for (SomeArgs args : mPendingOffload) {
+            final BroadcastRecord r = (BroadcastRecord) args.arg1;
+            dumpRecord("OFFLOAD", now, pw, r, args.argi1);
         }
         pw.decreaseIndent();
         pw.println();
     }
 
     @NeverCompile
-    private void dumpRecord(@UptimeMillisLong long now, @NonNull IndentingPrintWriter pw,
-            @NonNull BroadcastRecord record, int recordIndex, int blockedUntilTerminalCount) {
+    private void dumpRecord(@Nullable String flavor, @UptimeMillisLong long now,
+            @NonNull IndentingPrintWriter pw, @NonNull BroadcastRecord record, int recordIndex) {
         TimeUtils.formatDuration(record.enqueueTime, now, pw);
         pw.print(' ');
         pw.println(record.toShortString());
@@ -872,6 +886,10 @@
             pw.print(" at ");
             TimeUtils.formatDuration(record.scheduledTime[recordIndex], now, pw);
         }
+        if (flavor != null) {
+            pw.print(' ');
+            pw.print(flavor);
+        }
         final Object receiver = record.receivers.get(recordIndex);
         if (receiver instanceof BroadcastFilter) {
             final BroadcastFilter filter = (BroadcastFilter) receiver;
@@ -883,6 +901,7 @@
             pw.print(info.activityInfo.name);
         }
         pw.println();
+        final int blockedUntilTerminalCount = record.blockedUntilTerminalCount[recordIndex];
         if (blockedUntilTerminalCount != -1) {
             pw.print("    blocked until ");
             pw.print(blockedUntilTerminalCount);
diff --git a/services/core/java/com/android/server/am/BroadcastQueue.java b/services/core/java/com/android/server/am/BroadcastQueue.java
index 1e172fc..153ad1e 100644
--- a/services/core/java/com/android/server/am/BroadcastQueue.java
+++ b/services/core/java/com/android/server/am/BroadcastQueue.java
@@ -24,6 +24,7 @@
 import android.os.Bundle;
 import android.os.DropBoxManager;
 import android.os.Handler;
+import android.os.Trace;
 import android.util.Slog;
 import android.util.proto.ProtoOutputStream;
 
@@ -76,6 +77,30 @@
         }
     }
 
+    static void checkState(boolean expression, @NonNull String msg) {
+        if (!expression) {
+            throw new IllegalStateException(msg);
+        }
+    }
+
+    static void checkStateWtf(boolean expression, @NonNull String msg) {
+        if (!expression) {
+            Slog.wtf(TAG, new IllegalStateException(msg));
+        }
+    }
+
+    static int traceBegin(@NonNull String methodName) {
+        final int cookie = methodName.hashCode();
+        Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                TAG, methodName, cookie);
+        return cookie;
+    }
+
+    static void traceEnd(int cookie) {
+        Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
+                TAG, cookie);
+    }
+
     @Override
     public String toString() {
         return mQueueName;
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
index ffc54d9..454b284 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -1690,13 +1690,7 @@
                 System.identityHashCode(original));
         }
 
-        final ApplicationInfo info = original.callerApp != null ? original.callerApp.info : null;
-        final String callerPackage = info != null ? info.packageName : original.callerPackage;
-        if (callerPackage != null) {
-            mService.mHandler.obtainMessage(ActivityManagerService.DISPATCH_SENDING_BROADCAST_EVENT,
-                    original.callingUid, 0, callerPackage).sendToTarget();
-        }
-
+        mService.notifyBroadcastFinishedLocked(original);
         mHistory.addBroadcastToHistoryLocked(original);
     }
 
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index 9e9eb71..c3839a9 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -25,14 +25,12 @@
 import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__PROC_START_TYPE__PROCESS_START_TYPE_WARM;
 import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__MANIFEST;
 import static com.android.internal.util.FrameworkStatsLog.BROADCAST_DELIVERY_EVENT_REPORTED__RECEIVER_TYPE__RUNTIME;
-import static com.android.internal.util.Preconditions.checkState;
 import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST;
 import static com.android.server.am.BroadcastProcessQueue.insertIntoRunnableList;
 import static com.android.server.am.BroadcastProcessQueue.reasonToString;
 import static com.android.server.am.BroadcastProcessQueue.removeFromRunnableList;
 import static com.android.server.am.BroadcastRecord.deliveryStateToString;
 import static com.android.server.am.BroadcastRecord.getReceiverPackageName;
-import static com.android.server.am.BroadcastRecord.getReceiverPriority;
 import static com.android.server.am.BroadcastRecord.getReceiverProcessName;
 import static com.android.server.am.BroadcastRecord.getReceiverUid;
 import static com.android.server.am.BroadcastRecord.isDeliveryStateTerminal;
@@ -64,7 +62,6 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.SystemClock;
-import android.os.Trace;
 import android.os.UserHandle;
 import android.text.format.DateUtils;
 import android.util.IndentingPrintWriter;
@@ -144,14 +141,6 @@
         mRunning = new BroadcastProcessQueue[mConstants.MAX_RUNNING_PROCESS_QUEUES];
     }
 
-    // TODO: add support for replacing pending broadcasts
-    // TODO: add support for merging pending broadcasts
-
-    // TODO: consider reordering foreground broadcasts within queue
-
-    // TODO: pause queues when background services are running
-    // TODO: pause queues when processes are frozen
-
     /**
      * Map from UID to per-process broadcast queues. If a UID hosts more than
      * one process, each additional process is stored as a linked list using
@@ -222,12 +211,22 @@
     private static final int MSG_DELIVERY_TIMEOUT_HARD = 3;
     private static final int MSG_BG_ACTIVITY_START_TIMEOUT = 4;
     private static final int MSG_CHECK_HEALTH = 5;
+    private static final int MSG_FINISH_RECEIVER = 6;
 
     private void enqueueUpdateRunningList() {
         mLocalHandler.removeMessages(MSG_UPDATE_RUNNING_LIST);
         mLocalHandler.sendEmptyMessage(MSG_UPDATE_RUNNING_LIST);
     }
 
+    private void enqueueFinishReceiver(@NonNull BroadcastProcessQueue queue,
+            @DeliveryState int deliveryState, @NonNull String reason) {
+        final SomeArgs args = SomeArgs.obtain();
+        args.arg1 = queue;
+        args.argi1 = deliveryState;
+        args.arg2 = reason;
+        mLocalHandler.sendMessage(Message.obtain(mLocalHandler, MSG_FINISH_RECEIVER, args));
+    }
+
     private final Handler mLocalHandler;
 
     private final Handler.Callback mLocalCallback = (msg) -> {
@@ -266,6 +265,17 @@
                 }
                 return true;
             }
+            case MSG_FINISH_RECEIVER: {
+                synchronized (mService) {
+                    final SomeArgs args = (SomeArgs) msg.obj;
+                    final BroadcastProcessQueue queue = (BroadcastProcessQueue) args.arg1;
+                    final int deliveryState = args.argi1;
+                    final String reason = (String) args.arg2;
+                    args.recycle();
+                    finishReceiverLocked(queue, deliveryState, reason);
+                }
+                return true;
+            }
         }
         return false;
     };
@@ -349,7 +359,7 @@
         int avail = mRunning.length - getRunningSize();
         if (avail == 0) return;
 
-        final int cookie = traceBegin(TAG, "updateRunningList");
+        final int cookie = traceBegin("updateRunningList");
         final long now = SystemClock.uptimeMillis();
 
         // If someone is waiting for a state, everything is runnable now
@@ -412,21 +422,24 @@
             queue.runningTraceTrackName = TAG + ".mRunning[" + queueIndex + "]";
             queue.runningOomAdjusted = queue.isPendingManifest();
 
+            // If already warm, we can make OOM adjust request immediately;
+            // otherwise we need to wait until process becomes warm
+            if (processWarm) {
+                notifyStartedRunning(queue);
+                updateOomAdj |= queue.runningOomAdjusted;
+            }
+
             // If we're already warm, schedule next pending broadcast now;
             // otherwise we'll wait for the cold start to circle back around
             queue.makeActiveNextPending();
             if (processWarm) {
                 queue.traceProcessRunningBegin();
-                notifyStartedRunning(queue);
                 scheduleReceiverWarmLocked(queue);
             } else {
                 queue.traceProcessStartingBegin();
                 scheduleReceiverColdLocked(queue);
             }
 
-            // Only kick off an OOM adjustment pass if needed
-            updateOomAdj |= queue.runningOomAdjusted;
-
             // Move to considering next runnable queue
             queue = nextQueue;
         }
@@ -446,7 +459,7 @@
             });
         }
 
-        traceEnd(TAG, cookie);
+        traceEnd(cookie);
     }
 
     @Override
@@ -464,9 +477,13 @@
             // now; dispatch its next broadcast and clear the slot
             mRunningColdStart = null;
 
+            // Now that we're running warm, we can finally request that OOM
+            // adjust we've been waiting for
+            notifyStartedRunning(queue);
+            mService.updateOomAdjPendingTargetsLocked(OOM_ADJ_REASON_START_RECEIVER);
+
             queue.traceProcessEnd();
             queue.traceProcessRunningBegin();
-            notifyStartedRunning(queue);
             scheduleReceiverWarmLocked(queue);
 
             // We might be willing to kick off another cold start
@@ -509,7 +526,8 @@
         if (queue != null) {
             // If queue was running a broadcast, fail it
             if (queue.isActive()) {
-                finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE);
+                finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE,
+                        "onApplicationCleanupLocked");
             }
 
             // Skip any pending registered receivers, since the old process
@@ -537,6 +555,7 @@
     public void enqueueBroadcastLocked(@NonNull BroadcastRecord r) {
         if (DEBUG_BROADCAST) logv("Enqueuing " + r + " for " + r.receivers.size() + " receivers");
 
+        final int cookie = traceBegin("enqueueBroadcast");
         r.applySingletonPolicy(mService);
 
         final IntentFilter removeMatchingFilter = (r.options != null)
@@ -568,36 +587,11 @@
         r.enqueueRealTime = SystemClock.elapsedRealtime();
         r.enqueueClockTime = System.currentTimeMillis();
 
-        int lastPriority = 0;
-        int lastPriorityIndex = 0;
-
         for (int i = 0; i < r.receivers.size(); i++) {
             final Object receiver = r.receivers.get(i);
             final BroadcastProcessQueue queue = getOrCreateProcessQueue(
                     getReceiverProcessName(receiver), getReceiverUid(receiver));
-
-            final int blockedUntilTerminalCount;
-            if (r.ordered) {
-                // When sending an ordered broadcast, we need to block this
-                // receiver until all previous receivers have terminated
-                blockedUntilTerminalCount = i;
-            } else if (r.prioritized) {
-                // When sending a prioritized broadcast, we only need to wait
-                // for the previous traunch of receivers to be terminated
-                final int thisPriority = getReceiverPriority(receiver);
-                if ((i == 0) || (thisPriority != lastPriority)) {
-                    lastPriority = thisPriority;
-                    lastPriorityIndex = i;
-                    blockedUntilTerminalCount = i;
-                } else {
-                    blockedUntilTerminalCount = lastPriorityIndex;
-                }
-            } else {
-                // Otherwise we don't need to block at all
-                blockedUntilTerminalCount = -1;
-            }
-
-            queue.enqueueOrReplaceBroadcast(r, i, blockedUntilTerminalCount);
+            queue.enqueueOrReplaceBroadcast(r, i);
             updateRunnableList(queue);
             enqueueUpdateRunningList();
         }
@@ -606,6 +600,8 @@
         if (r.receivers.isEmpty()) {
             scheduleResultTo(r);
         }
+
+        traceEnd(cookie);
     }
 
     private void applyDeliveryGroupPolicy(@NonNull BroadcastRecord r) {
@@ -661,7 +657,8 @@
         // Ignore registered receivers from a previous PID
         if (receiver instanceof BroadcastFilter) {
             mRunningColdStart = null;
-            finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED);
+            enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_SKIPPED,
+                    "BroadcastFilter for cold app");
             return;
         }
 
@@ -683,7 +680,8 @@
                 hostingRecord, zygotePolicyFlags, allowWhileBooting, false);
         if (queue.app == null) {
             mRunningColdStart = null;
-            finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE);
+            enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_FAILURE,
+                    "startProcessLocked failed");
             return;
         }
     }
@@ -714,33 +712,37 @@
         // If someone already finished this broadcast, finish immediately
         final int oldDeliveryState = getDeliveryState(r, index);
         if (isDeliveryStateTerminal(oldDeliveryState)) {
-            finishReceiverLocked(queue, oldDeliveryState);
+            enqueueFinishReceiver(queue, oldDeliveryState, "already terminal state");
             return;
         }
 
         // Consider additional cases where we'd want to finish immediately
         if (app.isInFullBackup()) {
-            finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED);
+            enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_SKIPPED, "isInFullBackup");
             return;
         }
         if (mSkipPolicy.shouldSkip(r, receiver)) {
-            finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED);
+            enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_SKIPPED, "mSkipPolicy");
             return;
         }
         final Intent receiverIntent = r.getReceiverIntent(receiver);
         if (receiverIntent == null) {
-            finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED);
+            enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_SKIPPED, "getReceiverIntent");
             return;
         }
 
         // Ignore registered receivers from a previous PID
         if ((receiver instanceof BroadcastFilter)
                 && ((BroadcastFilter) receiver).receiverList.pid != app.getPid()) {
-            finishReceiverLocked(queue, BroadcastRecord.DELIVERY_SKIPPED);
+            enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_SKIPPED,
+                    "BroadcastFilter for mismatched PID");
             return;
         }
 
-        if (mService.mProcessesReady && !r.timeoutExempt) {
+        // Skip ANR tracking early during boot, when requested, or when we
+        // immediately assume delivery success
+        final boolean assumeDelivered = (receiver instanceof BroadcastFilter) && !r.ordered;
+        if (mService.mProcessesReady && !r.timeoutExempt && !assumeDelivered) {
             queue.lastCpuDelayTime = queue.app.getCpuDelayTime();
 
             final long timeout = r.isForeground() ? mFgConstants.TIMEOUT : mBgConstants.TIMEOUT;
@@ -768,7 +770,8 @@
         }
 
         if (DEBUG_BROADCAST) logv("Scheduling " + r + " to warm " + app);
-        setDeliveryState(queue, app, r, index, receiver, BroadcastRecord.DELIVERY_SCHEDULED);
+        setDeliveryState(queue, app, r, index, receiver, BroadcastRecord.DELIVERY_SCHEDULED,
+                "scheduleReceiverWarmLocked");
 
         final IApplicationThread thread = app.getOnewayThread();
         if (thread != null) {
@@ -782,8 +785,9 @@
 
                     // TODO: consider making registered receivers of unordered
                     // broadcasts report results to detect ANRs
-                    if (!r.ordered) {
-                        finishReceiverLocked(queue, BroadcastRecord.DELIVERY_DELIVERED);
+                    if (assumeDelivered) {
+                        enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_DELIVERED,
+                                "assuming delivered");
                     }
                 } else {
                     notifyScheduleReceiver(app, r, (ResolveInfo) receiver);
@@ -797,10 +801,11 @@
                 logw(msg);
                 app.scheduleCrashLocked(msg, CannotDeliverBroadcastException.TYPE_ID, null);
                 app.setKilled(true);
-                finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE);
+                enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_FAILURE, "remote app");
             }
         } else {
-            finishReceiverLocked(queue, BroadcastRecord.DELIVERY_FAILURE);
+            enqueueFinishReceiver(queue, BroadcastRecord.DELIVERY_FAILURE,
+                    "missing IApplicationThread");
         }
     }
 
@@ -809,9 +814,9 @@
      * ordered broadcast; assumes the sender is still a warm process.
      */
     private void scheduleResultTo(@NonNull BroadcastRecord r) {
-        if ((r.resultToApp == null) || (r.resultTo == null)) return;
+        if (r.resultTo == null) return;
         final ProcessRecord app = r.resultToApp;
-        final IApplicationThread thread = app.getOnewayThread();
+        final IApplicationThread thread = (app != null) ? app.getOnewayThread() : null;
         if (thread != null) {
             mService.mOomAdjuster.mCachedAppOptimizer.unfreezeTemporarily(
                     app, OOM_ADJ_REASON_FINISH_RECEIVER);
@@ -844,7 +849,8 @@
     }
 
     private void deliveryTimeoutHardLocked(@NonNull BroadcastProcessQueue queue) {
-        finishReceiverLocked(queue, BroadcastRecord.DELIVERY_TIMEOUT);
+        finishReceiverLocked(queue, BroadcastRecord.DELIVERY_TIMEOUT,
+                "deliveryTimeoutHardLocked");
     }
 
     @Override
@@ -871,24 +877,28 @@
             if (r.resultAbort) {
                 for (int i = r.terminalCount + 1; i < r.receivers.size(); i++) {
                     setDeliveryState(null, null, r, i, r.receivers.get(i),
-                            BroadcastRecord.DELIVERY_SKIPPED);
+                            BroadcastRecord.DELIVERY_SKIPPED, "resultAbort");
                 }
             }
         }
 
-        return finishReceiverLocked(queue, BroadcastRecord.DELIVERY_DELIVERED);
+        return finishReceiverLocked(queue, BroadcastRecord.DELIVERY_DELIVERED, "remote app");
     }
 
     private boolean finishReceiverLocked(@NonNull BroadcastProcessQueue queue,
-            @DeliveryState int deliveryState) {
-        checkState(queue.isActive(), "isActive");
+            @DeliveryState int deliveryState, @NonNull String reason) {
+        if (!queue.isActive()) {
+            logw("Ignoring finish; no active broadcast for " + queue);
+            return false;
+        }
 
+        final int cookie = traceBegin("finishReceiver");
         final ProcessRecord app = queue.app;
         final BroadcastRecord r = queue.getActive();
         final int index = queue.getActiveIndex();
         final Object receiver = r.receivers.get(index);
 
-        setDeliveryState(queue, app, r, index, receiver, deliveryState);
+        setDeliveryState(queue, app, r, index, receiver, deliveryState, reason);
 
         if (deliveryState == BroadcastRecord.DELIVERY_TIMEOUT) {
             r.anrCount++;
@@ -907,11 +917,12 @@
         final boolean shouldRetire =
                 (queue.getActiveCountSinceIdle() >= mConstants.MAX_RUNNING_ACTIVE_BROADCASTS);
 
+        final boolean res;
         if (queue.isRunnable() && queue.isProcessWarm() && !shouldRetire) {
             // We're on a roll; move onto the next broadcast for this process
             queue.makeActiveNextPending();
             scheduleReceiverWarmLocked(queue);
-            return true;
+            res = true;
         } else {
             // We've drained running broadcasts; maybe move back to runnable
             queue.makeActiveIdle();
@@ -925,8 +936,10 @@
             // Tell other OS components that app is not actively running, giving
             // a chance to update OOM adjustment
             notifyStoppedRunning(queue);
-            return false;
+            res = false;
         }
+        traceEnd(cookie);
+        return res;
     }
 
     /**
@@ -935,7 +948,8 @@
      */
     private void setDeliveryState(@Nullable BroadcastProcessQueue queue,
             @Nullable ProcessRecord app, @NonNull BroadcastRecord r, int index,
-            @NonNull Object receiver, @DeliveryState int newDeliveryState) {
+            @NonNull Object receiver, @DeliveryState int newDeliveryState, String reason) {
+        final int cookie = traceBegin("setDeliveryState");
         final int oldDeliveryState = getDeliveryState(r, index);
 
         // Only apply state when we haven't already reached a terminal state;
@@ -963,7 +977,7 @@
                 logw("Delivery state of " + r + " to " + receiver
                         + " via " + app + " changed from "
                         + deliveryStateToString(oldDeliveryState) + " to "
-                        + deliveryStateToString(newDeliveryState));
+                        + deliveryStateToString(newDeliveryState) + " because " + reason);
             }
 
             r.terminalCount++;
@@ -993,6 +1007,8 @@
                 enqueueUpdateRunningList();
             }
         }
+
+        traceEnd(cookie);
     }
 
     private @DeliveryState int getDeliveryState(@NonNull BroadcastRecord r, int index) {
@@ -1053,7 +1069,8 @@
      * of it matching a predicate.
      */
     private final BroadcastConsumer mBroadcastConsumerSkip = (r, i) -> {
-        setDeliveryState(null, null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_SKIPPED);
+        setDeliveryState(null, null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_SKIPPED,
+                "mBroadcastConsumerSkip");
     };
 
     /**
@@ -1061,7 +1078,8 @@
      * cancelled, usually as a result of it matching a predicate.
      */
     private final BroadcastConsumer mBroadcastConsumerSkipAndCanceled = (r, i) -> {
-        setDeliveryState(null, null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_SKIPPED);
+        setDeliveryState(null, null, r, i, r.receivers.get(i), BroadcastRecord.DELIVERY_SKIPPED,
+                "mBroadcastConsumerSkipAndCanceled");
         r.resultCode = Activity.RESULT_CANCELED;
         r.resultData = null;
         r.resultExtras = null;
@@ -1253,18 +1271,6 @@
         }
     }
 
-    private int traceBegin(String trackName, String methodName) {
-        final int cookie = methodName.hashCode();
-        Trace.asyncTraceForTrackBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
-                trackName, methodName, cookie);
-        return cookie;
-    }
-
-    private void traceEnd(String trackName, int cookie) {
-        Trace.asyncTraceForTrackEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER,
-                trackName, cookie);
-    }
-
     private void updateWarmProcess(@NonNull BroadcastProcessQueue queue) {
         if (!queue.isProcessWarm()) {
             queue.setProcess(mService.getProcessRecordLocked(queue.processName, queue.uid));
@@ -1396,6 +1402,7 @@
 
         final boolean recordFinished = (r.terminalCount == r.receivers.size());
         if (recordFinished) {
+            mService.notifyBroadcastFinishedLocked(r);
             mHistory.addBroadcastToHistoryLocked(r);
 
             r.finishTime = SystemClock.uptimeMillis();
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index 2a3c897..6ea2dee 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -96,6 +96,7 @@
     final @Nullable BroadcastOptions options; // BroadcastOptions supplied by caller
     final @NonNull List<Object> receivers;   // contains BroadcastFilter and ResolveInfo
     final @DeliveryState int[] delivery;   // delivery state of each receiver
+    final int[] blockedUntilTerminalCount; // blocked until count of each receiver
     @Nullable ProcessRecord resultToApp; // who receives final result if non-null
     @Nullable IIntentReceiver resultTo; // who receives final result if non-null
     boolean deferred;
@@ -375,6 +376,7 @@
         options = _options;
         receivers = (_receivers != null) ? _receivers : EMPTY_RECEIVERS;
         delivery = new int[_receivers != null ? _receivers.size() : 0];
+        blockedUntilTerminalCount = calculateBlockedUntilTerminalCount(receivers, _serialized);
         scheduledTime = new long[delivery.length];
         terminalTime = new long[delivery.length];
         resultToApp = _resultToApp;
@@ -385,7 +387,7 @@
         ordered = _serialized;
         sticky = _sticky;
         initialSticky = _initialSticky;
-        prioritized = isPrioritized(receivers);
+        prioritized = isPrioritized(blockedUntilTerminalCount, _serialized);
         userId = _userId;
         nextReceiver = 0;
         state = IDLE;
@@ -427,6 +429,7 @@
         options = from.options;
         receivers = from.receivers;
         delivery = from.delivery;
+        blockedUntilTerminalCount = from.blockedUntilTerminalCount;
         scheduledTime = from.scheduledTime;
         terminalTime = from.terminalTime;
         resultToApp = from.resultToApp;
@@ -617,6 +620,10 @@
         return (intent.getFlags() & Intent.FLAG_RECEIVER_NO_ABORT) != 0;
     }
 
+    boolean isOffload() {
+        return (intent.getFlags() & Intent.FLAG_RECEIVER_OFFLOAD) != 0;
+    }
+
     /**
      * Core policy determination about this broadcast's delivery prioritization
      */
@@ -686,22 +693,60 @@
     }
 
     /**
-     * Return if given receivers list has more than one traunch of priorities.
+     * Determine if the result of {@link #calculateBlockedUntilTerminalCount}
+     * has prioritized tranches of receivers.
      */
     @VisibleForTesting
-    static boolean isPrioritized(@NonNull List<Object> receivers) {
-        int firstPriority = 0;
-        for (int i = 0; i < receivers.size(); i++) {
-            final int thisPriority = getReceiverPriority(receivers.get(i));
-            if (i == 0) {
-                firstPriority = thisPriority;
-            } else if (thisPriority != firstPriority) {
-                return true;
-            }
-        }
-        return false;
+    static boolean isPrioritized(@NonNull int[] blockedUntilTerminalCount,
+            boolean ordered) {
+        return !ordered && (blockedUntilTerminalCount.length > 0)
+                && (blockedUntilTerminalCount[0] != -1);
     }
 
+    /**
+     * Calculate the {@link #terminalCount} that each receiver should be
+     * considered blocked until.
+     * <p>
+     * For example, in an ordered broadcast, receiver {@code N} is blocked until
+     * receiver {@code N-1} reaches a terminal state. Similarly, in a
+     * prioritized broadcast, receiver {@code N} is blocked until all receivers
+     * of a higher priority reach a terminal state.
+     * <p>
+     * When there are no terminal count constraints, the blocked value for each
+     * receiver is {@code -1}.
+     */
+    @VisibleForTesting
+    static @NonNull int[] calculateBlockedUntilTerminalCount(
+            @NonNull List<Object> receivers, boolean ordered) {
+        final int N = receivers.size();
+        final int[] blockedUntilTerminalCount = new int[N];
+        int lastPriority = 0;
+        int lastPriorityIndex = 0;
+        for (int i = 0; i < N; i++) {
+            if (ordered) {
+                // When sending an ordered broadcast, we need to block this
+                // receiver until all previous receivers have terminated
+                blockedUntilTerminalCount[i] = i;
+            } else {
+                // When sending a prioritized broadcast, we only need to wait
+                // for the previous tranche of receivers to be terminated
+                final int thisPriority = getReceiverPriority(receivers.get(i));
+                if ((i == 0) || (thisPriority != lastPriority)) {
+                    lastPriority = thisPriority;
+                    lastPriorityIndex = i;
+                    blockedUntilTerminalCount[i] = i;
+                } else {
+                    blockedUntilTerminalCount[i] = lastPriorityIndex;
+                }
+            }
+        }
+        // If the entire list is in the same priority tranche, mark as -1 to
+        // indicate that none of them need to wait
+        if (N > 0 && blockedUntilTerminalCount[N - 1] == 0) {
+            Arrays.fill(blockedUntilTerminalCount, -1);
+        }
+        return blockedUntilTerminalCount;
+    }
 
     static int getReceiverUid(@NonNull Object receiver) {
         if (receiver instanceof BroadcastFilter) {
diff --git a/services/core/java/com/android/server/am/BroadcastSkipPolicy.java b/services/core/java/com/android/server/am/BroadcastSkipPolicy.java
index 60fddf0..481ab17 100644
--- a/services/core/java/com/android/server/am/BroadcastSkipPolicy.java
+++ b/services/core/java/com/android/server/am/BroadcastSkipPolicy.java
@@ -21,6 +21,7 @@
 import static com.android.server.am.BroadcastQueue.TAG;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
@@ -42,6 +43,8 @@
 
 import com.android.internal.util.ArrayUtils;
 
+import java.util.Objects;
+
 /**
  * Policy logic that decides if delivery of a particular {@link BroadcastRecord}
  * should be skipped for a given {@link ResolveInfo} or {@link BroadcastFilter}.
@@ -51,8 +54,8 @@
 public class BroadcastSkipPolicy {
     private final ActivityManagerService mService;
 
-    public BroadcastSkipPolicy(ActivityManagerService service) {
-        mService = service;
+    public BroadcastSkipPolicy(@NonNull ActivityManagerService service) {
+        mService = Objects.requireNonNull(service);
     }
 
     /**
@@ -60,18 +63,39 @@
      * the given {@link BroadcastFilter} or {@link ResolveInfo}.
      */
     public boolean shouldSkip(@NonNull BroadcastRecord r, @NonNull Object target) {
-        if (target instanceof BroadcastFilter) {
-            return shouldSkip(r, (BroadcastFilter) target);
+        final String msg = shouldSkipMessage(r, target);
+        if (msg != null) {
+            Slog.w(TAG, msg);
+            return true;
         } else {
-            return shouldSkip(r, (ResolveInfo) target);
+            return false;
+        }
+    }
+
+    /**
+     * Determine if the given {@link BroadcastRecord} is eligible to be sent to
+     * the given {@link BroadcastFilter} or {@link ResolveInfo}.
+     *
+     * @return message indicating why the argument should be skipped, otherwise
+     *         {@code null} if it can proceed.
+     */
+    public @Nullable String shouldSkipMessage(@NonNull BroadcastRecord r, @NonNull Object target) {
+        if (target instanceof BroadcastFilter) {
+            return shouldSkipMessage(r, (BroadcastFilter) target);
+        } else {
+            return shouldSkipMessage(r, (ResolveInfo) target);
         }
     }
 
     /**
      * Determine if the given {@link BroadcastRecord} is eligible to be sent to
      * the given {@link ResolveInfo}.
+     *
+     * @return message indicating why the argument should be skipped, otherwise
+     *         {@code null} if it can proceed.
      */
-    public boolean shouldSkip(@NonNull BroadcastRecord r, @NonNull ResolveInfo info) {
+    private @Nullable String shouldSkipMessage(@NonNull BroadcastRecord r,
+            @NonNull ResolveInfo info) {
         final BroadcastOptions brOptions = r.options;
         final ComponentName component = new ComponentName(
                 info.activityInfo.applicationInfo.packageName,
@@ -82,58 +106,52 @@
                         < brOptions.getMinManifestReceiverApiLevel() ||
                 info.activityInfo.applicationInfo.targetSdkVersion
                         > brOptions.getMaxManifestReceiverApiLevel())) {
-            Slog.w(TAG, "Target SDK mismatch: receiver " + info.activityInfo
+            return "Target SDK mismatch: receiver " + info.activityInfo
                     + " targets " + info.activityInfo.applicationInfo.targetSdkVersion
                     + " but delivery restricted to ["
                     + brOptions.getMinManifestReceiverApiLevel() + ", "
                     + brOptions.getMaxManifestReceiverApiLevel()
-                    + "] broadcasting " + broadcastDescription(r, component));
-            return true;
+                    + "] broadcasting " + broadcastDescription(r, component);
         }
         if (brOptions != null &&
                 !brOptions.testRequireCompatChange(info.activityInfo.applicationInfo.uid)) {
-            Slog.w(TAG, "Compat change filtered: broadcasting " + broadcastDescription(r, component)
+            return "Compat change filtered: broadcasting " + broadcastDescription(r, component)
                     + " to uid " + info.activityInfo.applicationInfo.uid + " due to compat change "
-                    + r.options.getRequireCompatChangeId());
-            return true;
+                    + r.options.getRequireCompatChangeId();
         }
         if (!mService.validateAssociationAllowedLocked(r.callerPackage, r.callingUid,
                 component.getPackageName(), info.activityInfo.applicationInfo.uid)) {
-            Slog.w(TAG, "Association not allowed: broadcasting "
-                    + broadcastDescription(r, component));
-            return true;
+            return "Association not allowed: broadcasting "
+                    + broadcastDescription(r, component);
         }
         if (!mService.mIntentFirewall.checkBroadcast(r.intent, r.callingUid,
                 r.callingPid, r.resolvedType, info.activityInfo.applicationInfo.uid)) {
-            Slog.w(TAG, "Firewall blocked: broadcasting "
-                    + broadcastDescription(r, component));
-            return true;
+            return "Firewall blocked: broadcasting "
+                    + broadcastDescription(r, component);
         }
         int perm = checkComponentPermission(info.activityInfo.permission,
                 r.callingPid, r.callingUid, info.activityInfo.applicationInfo.uid,
                 info.activityInfo.exported);
         if (perm != PackageManager.PERMISSION_GRANTED) {
             if (!info.activityInfo.exported) {
-                Slog.w(TAG, "Permission Denial: broadcasting "
+                return "Permission Denial: broadcasting "
                         + broadcastDescription(r, component)
-                        + " is not exported from uid " + info.activityInfo.applicationInfo.uid);
+                        + " is not exported from uid " + info.activityInfo.applicationInfo.uid;
             } else {
-                Slog.w(TAG, "Permission Denial: broadcasting "
+                return "Permission Denial: broadcasting "
                         + broadcastDescription(r, component)
-                        + " requires " + info.activityInfo.permission);
+                        + " requires " + info.activityInfo.permission;
             }
-            return true;
         } else if (info.activityInfo.permission != null) {
             final int opCode = AppOpsManager.permissionToOpCode(info.activityInfo.permission);
             if (opCode != AppOpsManager.OP_NONE && mService.getAppOpsManager().noteOpNoThrow(opCode,
                     r.callingUid, r.callerPackage, r.callerFeatureId,
                     "Broadcast delivered to " + info.activityInfo.name)
                     != AppOpsManager.MODE_ALLOWED) {
-                Slog.w(TAG, "Appop Denial: broadcasting "
+                return "Appop Denial: broadcasting "
                         + broadcastDescription(r, component)
                         + " requires appop " + AppOpsManager.permissionToOp(
-                                info.activityInfo.permission));
-                return true;
+                                info.activityInfo.permission);
             }
         }
 
@@ -142,38 +160,34 @@
                     android.Manifest.permission.INTERACT_ACROSS_USERS,
                     info.activityInfo.applicationInfo.uid)
                             != PackageManager.PERMISSION_GRANTED) {
-                Slog.w(TAG, "Permission Denial: Receiver " + component.flattenToShortString()
+                return "Permission Denial: Receiver " + component.flattenToShortString()
                         + " requests FLAG_SINGLE_USER, but app does not hold "
-                        + android.Manifest.permission.INTERACT_ACROSS_USERS);
-                return true;
+                        + android.Manifest.permission.INTERACT_ACROSS_USERS;
             }
         }
         if (info.activityInfo.applicationInfo.isInstantApp()
                 && r.callingUid != info.activityInfo.applicationInfo.uid) {
-            Slog.w(TAG, "Instant App Denial: receiving "
+            return "Instant App Denial: receiving "
                     + r.intent
                     + " to " + component.flattenToShortString()
                     + " due to sender " + r.callerPackage
                     + " (uid " + r.callingUid + ")"
-                    + " Instant Apps do not support manifest receivers");
-            return true;
+                    + " Instant Apps do not support manifest receivers";
         }
         if (r.callerInstantApp
                 && (info.activityInfo.flags & ActivityInfo.FLAG_VISIBLE_TO_INSTANT_APP) == 0
                 && r.callingUid != info.activityInfo.applicationInfo.uid) {
-            Slog.w(TAG, "Instant App Denial: receiving "
+            return "Instant App Denial: receiving "
                     + r.intent
                     + " to " + component.flattenToShortString()
                     + " requires receiver have visibleToInstantApps set"
                     + " due to sender " + r.callerPackage
-                    + " (uid " + r.callingUid + ")");
-            return true;
+                    + " (uid " + r.callingUid + ")";
         }
         if (r.curApp != null && r.curApp.mErrorState.isCrashing()) {
             // If the target process is crashing, just skip it.
-            Slog.w(TAG, "Skipping deliver ordered [" + r.queue.toString() + "] " + r
-                    + " to " + r.curApp + ": process crashing");
-            return true;
+            return "Skipping deliver ordered [" + r.queue.toString() + "] " + r
+                    + " to " + r.curApp + ": process crashing";
         }
 
         boolean isAvailable = false;
@@ -183,15 +197,13 @@
                     UserHandle.getUserId(info.activityInfo.applicationInfo.uid));
         } catch (Exception e) {
             // all such failures mean we skip this receiver
-            Slog.w(TAG, "Exception getting recipient info for "
-                    + info.activityInfo.packageName, e);
+            return "Exception getting recipient info for "
+                    + info.activityInfo.packageName;
         }
         if (!isAvailable) {
-            Slog.w(TAG,
-                    "Skipping delivery to " + info.activityInfo.packageName + " / "
+            return "Skipping delivery to " + info.activityInfo.packageName + " / "
                     + info.activityInfo.applicationInfo.uid
-                    + " : package no longer available");
-            return true;
+                    + " : package no longer available";
         }
 
         // If permissions need a review before any of the app components can run, we drop
@@ -201,10 +213,8 @@
         if (!requestStartTargetPermissionsReviewIfNeededLocked(r,
                 info.activityInfo.packageName, UserHandle.getUserId(
                         info.activityInfo.applicationInfo.uid))) {
-            Slog.w(TAG,
-                    "Skipping delivery: permission review required for "
-                            + broadcastDescription(r, component));
-            return true;
+            return "Skipping delivery: permission review required for "
+                            + broadcastDescription(r, component);
         }
 
         final int allowed = mService.getAppStartModeLOSP(
@@ -216,10 +226,9 @@
             // to it and the app is in a state that should not receive it
             // (depending on how getAppStartModeLOSP has determined that).
             if (allowed == ActivityManager.APP_START_MODE_DISABLED) {
-                Slog.w(TAG, "Background execution disabled: receiving "
+                return "Background execution disabled: receiving "
                         + r.intent + " to "
-                        + component.flattenToShortString());
-                return true;
+                        + component.flattenToShortString();
             } else if (((r.intent.getFlags()&Intent.FLAG_RECEIVER_EXCLUDE_BACKGROUND) != 0)
                     || (r.intent.getComponent() == null
                         && r.intent.getPackage() == null
@@ -228,10 +237,9 @@
                         && !isSignaturePerm(r.requiredPermissions))) {
                 mService.addBackgroundCheckViolationLocked(r.intent.getAction(),
                         component.getPackageName());
-                Slog.w(TAG, "Background execution not allowed: receiving "
+                return "Background execution not allowed: receiving "
                         + r.intent + " to "
-                        + component.flattenToShortString());
-                return true;
+                        + component.flattenToShortString();
             }
         }
 
@@ -239,10 +247,8 @@
                 && !mService.mUserController
                 .isUserRunning(UserHandle.getUserId(info.activityInfo.applicationInfo.uid),
                         0 /* flags */)) {
-            Slog.w(TAG,
-                    "Skipping delivery to " + info.activityInfo.packageName + " / "
-                            + info.activityInfo.applicationInfo.uid + " : user is not running");
-            return true;
+            return "Skipping delivery to " + info.activityInfo.packageName + " / "
+                            + info.activityInfo.applicationInfo.uid + " : user is not running";
         }
 
         if (r.excludedPermissions != null && r.excludedPermissions.length > 0) {
@@ -268,13 +274,15 @@
                                 info.activityInfo.applicationInfo.uid,
                                 info.activityInfo.packageName)
                             == AppOpsManager.MODE_ALLOWED)) {
-                        return true;
+                        return "Skipping delivery to " + info.activityInfo.packageName
+                                + " due to excluded permission " + excludedPermission;
                     }
                 } else {
                     // When there is no app op associated with the permission,
                     // skip when permission is granted.
                     if (perm == PackageManager.PERMISSION_GRANTED) {
-                        return true;
+                        return "Skipping delivery to " + info.activityInfo.packageName
+                                + " due to excluded permission " + excludedPermission;
                     }
                 }
             }
@@ -283,13 +291,12 @@
         // Check that the receiver does *not* belong to any of the excluded packages
         if (r.excludedPackages != null && r.excludedPackages.length > 0) {
             if (ArrayUtils.contains(r.excludedPackages, component.getPackageName())) {
-                Slog.w(TAG, "Skipping delivery of excluded package "
+                return "Skipping delivery of excluded package "
                         + r.intent + " to "
                         + component.flattenToShortString()
                         + " excludes package " + component.getPackageName()
                         + " due to sender " + r.callerPackage
-                        + " (uid " + r.callingUid + ")");
-                return true;
+                        + " (uid " + r.callingUid + ")";
             }
         }
 
@@ -307,95 +314,94 @@
                     perm = PackageManager.PERMISSION_DENIED;
                 }
                 if (perm != PackageManager.PERMISSION_GRANTED) {
-                    Slog.w(TAG, "Permission Denial: receiving "
+                    return "Permission Denial: receiving "
                             + r.intent + " to "
                             + component.flattenToShortString()
                             + " requires " + requiredPermission
                             + " due to sender " + r.callerPackage
-                            + " (uid " + r.callingUid + ")");
-                    return true;
+                            + " (uid " + r.callingUid + ")";
                 }
                 int appOp = AppOpsManager.permissionToOpCode(requiredPermission);
                 if (appOp != AppOpsManager.OP_NONE && appOp != r.appOp) {
                     if (!noteOpForManifestReceiver(appOp, r, info, component)) {
-                        return true;
+                        return "Skipping delivery to " + info.activityInfo.packageName
+                                + " due to required appop " + appOp;
                     }
                 }
             }
         }
         if (r.appOp != AppOpsManager.OP_NONE) {
             if (!noteOpForManifestReceiver(r.appOp, r, info, component)) {
-                return true;
+                return "Skipping delivery to " + info.activityInfo.packageName
+                        + " due to required appop " + r.appOp;
             }
         }
 
-        return false;
+        return null;
     }
 
     /**
      * Determine if the given {@link BroadcastRecord} is eligible to be sent to
      * the given {@link BroadcastFilter}.
+     *
+     * @return message indicating why the argument should be skipped, otherwise
+     *         {@code null} if it can proceed.
      */
-    public boolean shouldSkip(@NonNull BroadcastRecord r, @NonNull BroadcastFilter filter) {
+    private @Nullable String shouldSkipMessage(@NonNull BroadcastRecord r,
+            @NonNull BroadcastFilter filter) {
         if (r.options != null && !r.options.testRequireCompatChange(filter.owningUid)) {
-            Slog.w(TAG, "Compat change filtered: broadcasting " + r.intent.toString()
+            return "Compat change filtered: broadcasting " + r.intent.toString()
                     + " to uid " + filter.owningUid + " due to compat change "
-                    + r.options.getRequireCompatChangeId());
-            return true;
+                    + r.options.getRequireCompatChangeId();
         }
         if (!mService.validateAssociationAllowedLocked(r.callerPackage, r.callingUid,
                 filter.packageName, filter.owningUid)) {
-            Slog.w(TAG, "Association not allowed: broadcasting "
+            return "Association not allowed: broadcasting "
                     + r.intent.toString()
                     + " from " + r.callerPackage + " (pid=" + r.callingPid
                     + ", uid=" + r.callingUid + ") to " + filter.packageName + " through "
-                    + filter);
-            return true;
+                    + filter;
         }
         if (!mService.mIntentFirewall.checkBroadcast(r.intent, r.callingUid,
                 r.callingPid, r.resolvedType, filter.receiverList.uid)) {
-            Slog.w(TAG, "Firewall blocked: broadcasting "
+            return "Firewall blocked: broadcasting "
                     + r.intent.toString()
                     + " from " + r.callerPackage + " (pid=" + r.callingPid
                     + ", uid=" + r.callingUid + ") to " + filter.packageName + " through "
-                    + filter);
-            return true;
+                    + filter;
         }
         // Check that the sender has permission to send to this receiver
         if (filter.requiredPermission != null) {
             int perm = checkComponentPermission(filter.requiredPermission,
                     r.callingPid, r.callingUid, -1, true);
             if (perm != PackageManager.PERMISSION_GRANTED) {
-                Slog.w(TAG, "Permission Denial: broadcasting "
+                return "Permission Denial: broadcasting "
                         + r.intent.toString()
                         + " from " + r.callerPackage + " (pid="
                         + r.callingPid + ", uid=" + r.callingUid + ")"
                         + " requires " + filter.requiredPermission
-                        + " due to registered receiver " + filter);
-                return true;
+                        + " due to registered receiver " + filter;
             } else {
                 final int opCode = AppOpsManager.permissionToOpCode(filter.requiredPermission);
                 if (opCode != AppOpsManager.OP_NONE
                         && mService.getAppOpsManager().noteOpNoThrow(opCode, r.callingUid,
                         r.callerPackage, r.callerFeatureId, "Broadcast sent to protected receiver")
                         != AppOpsManager.MODE_ALLOWED) {
-                    Slog.w(TAG, "Appop Denial: broadcasting "
+                    return "Appop Denial: broadcasting "
                             + r.intent.toString()
                             + " from " + r.callerPackage + " (pid="
                             + r.callingPid + ", uid=" + r.callingUid + ")"
                             + " requires appop " + AppOpsManager.permissionToOp(
                                     filter.requiredPermission)
-                            + " due to registered receiver " + filter);
-                    return true;
+                            + " due to registered receiver " + filter;
                 }
             }
         }
 
         if ((filter.receiverList.app == null || filter.receiverList.app.isKilled()
                 || filter.receiverList.app.mErrorState.isCrashing())) {
-            Slog.w(TAG, "Skipping deliver [" + r.queue.toString() + "] " + r
-                    + " to " + filter.receiverList + ": process gone or crashing");
-            return true;
+            return "Skipping deliver [" + r.queue.toString() + "] " + r
+                    + " to " + filter.receiverList + ": process gone or crashing";
         }
 
         // Ensure that broadcasts are only sent to other Instant Apps if they are marked as
@@ -405,28 +411,26 @@
 
         if (!visibleToInstantApps && filter.instantApp
                 && filter.receiverList.uid != r.callingUid) {
-            Slog.w(TAG, "Instant App Denial: receiving "
+            return "Instant App Denial: receiving "
                     + r.intent.toString()
                     + " to " + filter.receiverList.app
                     + " (pid=" + filter.receiverList.pid
                     + ", uid=" + filter.receiverList.uid + ")"
                     + " due to sender " + r.callerPackage
                     + " (uid " + r.callingUid + ")"
-                    + " not specifying FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS");
-            return true;
+                    + " not specifying FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS";
         }
 
         if (!filter.visibleToInstantApp && r.callerInstantApp
                 && filter.receiverList.uid != r.callingUid) {
-            Slog.w(TAG, "Instant App Denial: receiving "
+            return "Instant App Denial: receiving "
                     + r.intent.toString()
                     + " to " + filter.receiverList.app
                     + " (pid=" + filter.receiverList.pid
                     + ", uid=" + filter.receiverList.uid + ")"
                     + " requires receiver be visible to instant apps"
                     + " due to sender " + r.callerPackage
-                    + " (uid " + r.callingUid + ")");
-            return true;
+                    + " (uid " + r.callingUid + ")";
         }
 
         // Check that the receiver has the required permission(s) to receive this broadcast.
@@ -436,15 +440,14 @@
                 int perm = checkComponentPermission(requiredPermission,
                         filter.receiverList.pid, filter.receiverList.uid, -1, true);
                 if (perm != PackageManager.PERMISSION_GRANTED) {
-                    Slog.w(TAG, "Permission Denial: receiving "
+                    return "Permission Denial: receiving "
                             + r.intent.toString()
                             + " to " + filter.receiverList.app
                             + " (pid=" + filter.receiverList.pid
                             + ", uid=" + filter.receiverList.uid + ")"
                             + " requires " + requiredPermission
                             + " due to sender " + r.callerPackage
-                            + " (uid " + r.callingUid + ")");
-                    return true;
+                            + " (uid " + r.callingUid + ")";
                 }
                 int appOp = AppOpsManager.permissionToOpCode(requiredPermission);
                 if (appOp != AppOpsManager.OP_NONE && appOp != r.appOp
@@ -452,7 +455,7 @@
                         filter.receiverList.uid, filter.packageName, filter.featureId,
                         "Broadcast delivered to registered receiver " + filter.receiverId)
                         != AppOpsManager.MODE_ALLOWED) {
-                    Slog.w(TAG, "Appop Denial: receiving "
+                    return "Appop Denial: receiving "
                             + r.intent.toString()
                             + " to " + filter.receiverList.app
                             + " (pid=" + filter.receiverList.pid
@@ -460,8 +463,7 @@
                             + " requires appop " + AppOpsManager.permissionToOp(
                             requiredPermission)
                             + " due to sender " + r.callerPackage
-                            + " (uid " + r.callingUid + ")");
-                    return true;
+                            + " (uid " + r.callingUid + ")";
                 }
             }
         }
@@ -469,14 +471,13 @@
             int perm = checkComponentPermission(null,
                     filter.receiverList.pid, filter.receiverList.uid, -1, true);
             if (perm != PackageManager.PERMISSION_GRANTED) {
-                Slog.w(TAG, "Permission Denial: security check failed when receiving "
+                return "Permission Denial: security check failed when receiving "
                         + r.intent.toString()
                         + " to " + filter.receiverList.app
                         + " (pid=" + filter.receiverList.pid
                         + ", uid=" + filter.receiverList.uid + ")"
                         + " due to sender " + r.callerPackage
-                        + " (uid " + r.callingUid + ")");
-                return true;
+                        + " (uid " + r.callingUid + ")";
             }
         }
         // Check that the receiver does *not* have any excluded permissions
@@ -496,7 +497,7 @@
                                     filter.receiverList.uid,
                                     filter.packageName)
                                     == AppOpsManager.MODE_ALLOWED)) {
-                        Slog.w(TAG, "Appop Denial: receiving "
+                        return "Appop Denial: receiving "
                                 + r.intent.toString()
                                 + " to " + filter.receiverList.app
                                 + " (pid=" + filter.receiverList.pid
@@ -504,22 +505,20 @@
                                 + " excludes appop " + AppOpsManager.permissionToOp(
                                 excludedPermission)
                                 + " due to sender " + r.callerPackage
-                                + " (uid " + r.callingUid + ")");
-                        return true;
+                                + " (uid " + r.callingUid + ")";
                     }
                 } else {
                     // When there is no app op associated with the permission,
                     // skip when permission is granted.
                     if (perm == PackageManager.PERMISSION_GRANTED) {
-                        Slog.w(TAG, "Permission Denial: receiving "
+                        return "Permission Denial: receiving "
                                 + r.intent.toString()
                                 + " to " + filter.receiverList.app
                                 + " (pid=" + filter.receiverList.pid
                                 + ", uid=" + filter.receiverList.uid + ")"
                                 + " excludes " + excludedPermission
                                 + " due to sender " + r.callerPackage
-                                + " (uid " + r.callingUid + ")");
-                        return true;
+                                + " (uid " + r.callingUid + ")";
                     }
                 }
             }
@@ -528,15 +527,14 @@
         // Check that the receiver does *not* belong to any of the excluded packages
         if (r.excludedPackages != null && r.excludedPackages.length > 0) {
             if (ArrayUtils.contains(r.excludedPackages, filter.packageName)) {
-                Slog.w(TAG, "Skipping delivery of excluded package "
+                return "Skipping delivery of excluded package "
                         + r.intent.toString()
                         + " to " + filter.receiverList.app
                         + " (pid=" + filter.receiverList.pid
                         + ", uid=" + filter.receiverList.uid + ")"
                         + " excludes package " + filter.packageName
                         + " due to sender " + r.callerPackage
-                        + " (uid " + r.callingUid + ")");
-                return true;
+                        + " (uid " + r.callingUid + ")";
             }
         }
 
@@ -546,15 +544,14 @@
                 filter.receiverList.uid, filter.packageName, filter.featureId,
                 "Broadcast delivered to registered receiver " + filter.receiverId)
                 != AppOpsManager.MODE_ALLOWED) {
-            Slog.w(TAG, "Appop Denial: receiving "
+            return "Appop Denial: receiving "
                     + r.intent.toString()
                     + " to " + filter.receiverList.app
                     + " (pid=" + filter.receiverList.pid
                     + ", uid=" + filter.receiverList.uid + ")"
                     + " requires appop " + AppOpsManager.opToName(r.appOp)
                     + " due to sender " + r.callerPackage
-                    + " (uid " + r.callingUid + ")");
-            return true;
+                    + " (uid " + r.callingUid + ")";
         }
 
         // Ensure that broadcasts are only sent to other apps if they are explicitly marked as
@@ -562,15 +559,14 @@
         if (!filter.exported && checkComponentPermission(null, r.callingPid,
                 r.callingUid, filter.receiverList.uid, filter.exported)
                 != PackageManager.PERMISSION_GRANTED) {
-            Slog.w(TAG, "Exported Denial: sending "
+            return "Exported Denial: sending "
                     + r.intent.toString()
                     + ", action: " + r.intent.getAction()
                     + " from " + r.callerPackage
                     + " (uid=" + r.callingUid + ")"
                     + " due to receiver " + filter.receiverList.app
                     + " (uid " + filter.receiverList.uid + ")"
-                    + " not specifying RECEIVER_EXPORTED");
-            return true;
+                    + " not specifying RECEIVER_EXPORTED";
         }
 
         // If permissions need a review before any of the app components can run, we drop
@@ -579,10 +575,10 @@
         // broadcast.
         if (!requestStartTargetPermissionsReviewIfNeededLocked(r, filter.packageName,
                 filter.owningUserId)) {
-            return true;
+            return "Skipping delivery to " + filter.packageName + " due to permissions review";
         }
 
-        return false;
+        return null;
     }
 
     private static String broadcastDescription(BroadcastRecord r, ComponentName component) {
diff --git a/services/core/java/com/android/server/am/EventLogTags.logtags b/services/core/java/com/android/server/am/EventLogTags.logtags
index dec8b62..60e6754 100644
--- a/services/core/java/com/android/server/am/EventLogTags.logtags
+++ b/services/core/java/com/android/server/am/EventLogTags.logtags
@@ -116,7 +116,7 @@
 30086 ssm_user_stopping (userId|1|5),(visibilityChanged|1)
 30087 ssm_user_stopped (userId|1|5)
 30088 ssm_user_completed_event (userId|1|5),(eventFlag|1|5)
-30089 ssm_user_visible (userId|1|5)
+30089 ssm_user_visibility_changed (userId|1|5),(visible|1)
 
 # Foreground service start/stop events.
 30100 am_foreground_service_start (User|1|5),(Component Name|3),(allowWhileInUse|1),(startReasonCode|3),(targetSdk|1|1),(callerTargetSdk|1|1),(notificationWasDeferred|1),(notificationShown|1),(durationMs|1|3),(startForegroundCount|1|1),(stopReason|3)
diff --git a/services/core/java/com/android/server/am/PendingIntentRecord.java b/services/core/java/com/android/server/am/PendingIntentRecord.java
index 740efbc..3f5f3bc 100644
--- a/services/core/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/core/java/com/android/server/am/PendingIntentRecord.java
@@ -18,7 +18,6 @@
 
 import static android.app.ActivityManager.START_SUCCESS;
 
-import static com.android.server.am.ActivityManagerDebugConfig.DEBUG_BROADCAST_LIGHT;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
 
@@ -36,7 +35,6 @@
 import android.os.IBinder;
 import android.os.PowerWhitelistManager;
 import android.os.PowerWhitelistManager.ReasonCode;
-import android.os.Process;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.TransactionTooLargeException;
@@ -441,17 +439,8 @@
         // Only system senders can declare a broadcast to be alarm-originated.  We check
         // this here rather than in the general case handling below to fail before the other
         // invocation side effects such as allowlisting.
-        if (options != null && callingUid != Process.SYSTEM_UID
-                && key.type == ActivityManager.INTENT_SENDER_BROADCAST) {
-            if (options.containsKey(BroadcastOptions.KEY_ALARM_BROADCAST)
-                    || options.containsKey(BroadcastOptions.KEY_INTERACTIVE_BROADCAST)) {
-                if (DEBUG_BROADCAST_LIGHT) {
-                    Slog.w(TAG, "Non-system caller " + callingUid
-                            + " may not flag broadcast as alarm or interactive");
-                }
-                throw new SecurityException(
-                        "Non-system callers may not flag broadcasts as alarm or interactive");
-            }
+        if (key.type == ActivityManager.INTENT_SENDER_BROADCAST) {
+            controller.mAmInternal.enforceBroadcastOptionsPermissions(options, callingUid);
         }
 
         final long origId = Binder.clearCallingIdentity();
@@ -490,6 +479,11 @@
 
             final IApplicationThread finishedReceiverThread = caller;
             boolean sendFinish = finishedReceiver != null;
+            if ((finishedReceiver != null) && (finishedReceiverThread == null)) {
+                Slog.w(TAG, "Sending of " + intent + " from " + Binder.getCallingUid()
+                        + " requested resultTo without an IApplicationThread!", new Throwable());
+            }
+
             int userId = key.userId;
             if (userId == UserHandle.USER_CURRENT) {
                 userId = controller.mUserController.getCurrentOrTargetUserId();
diff --git a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
index 71d39964..68d906b 100644
--- a/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
+++ b/services/core/java/com/android/server/am/ProcessErrorStateRecord.java
@@ -44,7 +44,7 @@
 import android.provider.Settings;
 import android.util.EventLog;
 import android.util.Slog;
-import android.util.SparseArray;
+import android.util.SparseBooleanArray;
 
 import com.android.internal.annotations.CompositeRWLock;
 import com.android.internal.annotations.GuardedBy;
@@ -62,6 +62,12 @@
 import java.io.StringWriter;
 import java.util.ArrayList;
 import java.util.UUID;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.concurrent.atomic.AtomicLong;
+
+
 /**
  * The error state of the process, such as if it's crashing/ANR etc.
  */
@@ -257,12 +263,13 @@
     void appNotResponding(String activityShortComponentName, ApplicationInfo aInfo,
             String parentShortComponentName, WindowProcessController parentProcess,
             boolean aboveSystem, TimeoutRecord timeoutRecord,
-            boolean onlyDumpSelf) {
+            ExecutorService auxiliaryTaskExecutor, boolean onlyDumpSelf) {
         String annotation = timeoutRecord.mReason;
         AnrLatencyTracker latencyTracker = timeoutRecord.mLatencyTracker;
+        Future<?> updateCpuStatsNowFirstCall = null;
 
         ArrayList<Integer> firstPids = new ArrayList<>(5);
-        SparseArray<Boolean> lastPids = new SparseArray<>(20);
+        SparseBooleanArray lastPids = new SparseBooleanArray(20);
 
         mApp.getWindowProcessController().appEarlyNotResponding(annotation, () -> {
             latencyTracker.waitingOnAMSLockStarted();
@@ -275,10 +282,15 @@
         });
 
         long anrTime = SystemClock.uptimeMillis();
+
         if (isMonitorCpuUsage()) {
-            latencyTracker.updateCpuStatsNowCalled();
-            mService.updateCpuStatsNow();
-            latencyTracker.updateCpuStatsNowReturned();
+            updateCpuStatsNowFirstCall = auxiliaryTaskExecutor.submit(
+                    () -> {
+                    latencyTracker.updateCpuStatsNowCalled();
+                    mService.updateCpuStatsNow();
+                    latencyTracker.updateCpuStatsNowReturned();
+                });
+
         }
 
         final boolean isSilentAnr;
@@ -367,7 +379,7 @@
                                 firstPids.add(myPid);
                                 if (DEBUG_ANR) Slog.i(TAG, "Adding likely IME: " + r);
                             } else {
-                                lastPids.put(myPid, Boolean.TRUE);
+                                lastPids.put(myPid, true);
                                 if (DEBUG_ANR) Slog.i(TAG, "Adding ANR proc: " + r);
                             }
                         }
@@ -430,41 +442,59 @@
         report.append(currentPsiState);
         ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(true);
 
-        latencyTracker.nativePidCollectionStarted();
-        // don't dump native PIDs for background ANRs unless it is the process of interest
-        String[] nativeProcs = null;
-        if (isSilentAnr || onlyDumpSelf) {
-            for (int i = 0; i < NATIVE_STACKS_OF_INTEREST.length; i++) {
-                if (NATIVE_STACKS_OF_INTEREST[i].equals(mApp.processName)) {
-                    nativeProcs = new String[] { mApp.processName };
-                    break;
-                }
-            }
-        } else {
-            nativeProcs = NATIVE_STACKS_OF_INTEREST;
-        }
+        // We push the native pids collection task to the helper thread through
+        // the Anr auxiliary task executor, and wait on it later after dumping the first pids
+        Future<ArrayList<Integer>> nativePidsFuture =
+                auxiliaryTaskExecutor.submit(
+                    () -> {
+                        latencyTracker.nativePidCollectionStarted();
+                        // don't dump native PIDs for background ANRs unless
+                        // it is the process of interest
+                        String[] nativeProcs = null;
+                        if (isSilentAnr || onlyDumpSelf) {
+                            for (int i = 0; i < NATIVE_STACKS_OF_INTEREST.length; i++) {
+                                if (NATIVE_STACKS_OF_INTEREST[i].equals(mApp.processName)) {
+                                    nativeProcs = new String[] { mApp.processName };
+                                    break;
+                                }
+                            }
+                        } else {
+                            nativeProcs = NATIVE_STACKS_OF_INTEREST;
+                        }
 
-        int[] pids = nativeProcs == null ? null : Process.getPidsForCommands(nativeProcs);
-        ArrayList<Integer> nativePids = null;
+                        int[] pids = nativeProcs == null
+                                ? null : Process.getPidsForCommands(nativeProcs);
+                        ArrayList<Integer> nativePids = null;
 
-        if (pids != null) {
-            nativePids = new ArrayList<>(pids.length);
-            for (int i : pids) {
-                nativePids.add(i);
-            }
-        }
-        latencyTracker.nativePidCollectionEnded();
+                        if (pids != null) {
+                            nativePids = new ArrayList<>(pids.length);
+                            for (int i : pids) {
+                                nativePids.add(i);
+                            }
+                        }
+                        latencyTracker.nativePidCollectionEnded();
+                        return nativePids;
+                    });
+
         // For background ANRs, don't pass the ProcessCpuTracker to
         // avoid spending 1/2 second collecting stats to rank lastPids.
         StringWriter tracesFileException = new StringWriter();
         // To hold the start and end offset to the ANR trace file respectively.
-        final long[] offsets = new long[2];
+        final AtomicLong firstPidEndOffset = new AtomicLong(-1);
         File tracesFile = ActivityManagerService.dumpStackTraces(firstPids,
                 isSilentAnr ? null : processCpuTracker, isSilentAnr ? null : lastPids,
-                nativePids, tracesFileException, offsets, annotation, criticalEventLog,
-                latencyTracker);
+                nativePidsFuture, tracesFileException, firstPidEndOffset, annotation,
+                criticalEventLog, auxiliaryTaskExecutor, latencyTracker);
 
         if (isMonitorCpuUsage()) {
+            // Wait for the first call to finish
+            try {
+                updateCpuStatsNowFirstCall.get();
+            } catch (ExecutionException e) {
+                Slog.w(TAG, "Failed to update the CPU stats", e.getCause());
+            } catch (InterruptedException e) {
+                Slog.w(TAG, "Interrupted while updating the CPU stats", e);
+            }
             mService.updateCpuStatsNow();
             mService.mAppProfiler.printCurrentCpuState(report, anrTime);
             info.append(processCpuTracker.printCurrentLoad());
@@ -478,10 +508,14 @@
         if (tracesFile == null) {
             // There is no trace file, so dump (only) the alleged culprit's threads to the log
             Process.sendSignal(pid, Process.SIGNAL_QUIT);
-        } else if (offsets[1] > 0) {
+        } else if (firstPidEndOffset.get() > 0) {
             // We've dumped into the trace file successfully
+            // We pass the start and end offsets of the first section of
+            // the ANR file (the headers and first process dump)
+            final long startOffset = 0L;
+            final long endOffset = firstPidEndOffset.get();
             mService.mProcessList.mAppExitInfoTracker.scheduleLogAnrTrace(
-                    pid, mApp.uid, mApp.getPackageList(), tracesFile, offsets[0], offsets[1]);
+                    pid, mApp.uid, mApp.getPackageList(), tracesFile, startOffset, endOffset);
         }
 
         // Check if package is still being loaded
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index dcc7a8e..af55980 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -120,6 +120,7 @@
 import com.android.server.SystemServiceManager;
 import com.android.server.am.UserState.KeyEvictedCallback;
 import com.android.server.pm.UserManagerInternal;
+import com.android.server.pm.UserManagerInternal.UserLifecycleListener;
 import com.android.server.pm.UserManagerService;
 import com.android.server.utils.Slogf;
 import com.android.server.utils.TimingsTraceAndSlog;
@@ -320,8 +321,12 @@
     @GuardedBy("mLock")
     private int[] mStartedUserArray = new int[] { 0 };
 
-    // If there are multiple profiles for the current user, their ids are here
-    // Currently only the primary user can have managed profiles
+    /**
+     * Contains the current user and its profiles (if any).
+     *
+     * <p><b>NOTE: </b>it lists all profiles, regardless of their running state (i.e., they're in
+     * this list even if not running).
+     */
     @GuardedBy("mLock")
     private int[] mCurrentProfileIds = new int[] {};
 
@@ -436,6 +441,18 @@
     @GuardedBy("mLock")
     private final SparseBooleanArray mVisibleUsers = new SparseBooleanArray();
 
+    private final UserLifecycleListener mUserLifecycleListener = new UserLifecycleListener() {
+        @Override
+        public void onUserCreated(UserInfo user, Object token) {
+            onUserAdded(user);
+        }
+
+        @Override
+        public void onUserRemoved(UserInfo user) {
+            UserController.this.onUserRemoved(user.id);
+        }
+    };
+
     UserController(ActivityManagerService service) {
         this(new Injector(service));
     }
@@ -1667,7 +1684,7 @@
                     userSwitchUiEnabled = mUserSwitchUiEnabled;
                 }
                 mInjector.updateUserConfiguration();
-                updateCurrentProfileIds();
+                updateProfileRelatedCaches();
                 mInjector.getWindowManager().setCurrentUser(userId);
                 mInjector.reportCurWakefulnessUsageEvent();
                 // Once the internal notion of the active user has switched, we lock the device
@@ -1681,7 +1698,7 @@
                 }
             } else {
                 final Integer currentUserIdInt = mCurrentUserId;
-                updateCurrentProfileIds();
+                updateProfileRelatedCaches();
                 synchronized (mLock) {
                     mUserLru.remove(currentUserIdInt);
                     mUserLru.add(currentUserIdInt);
@@ -2168,14 +2185,12 @@
         mHandler.sendMessage(mHandler.obtainMessage(COMPLETE_USER_SWITCH_MSG, newUserId, 0));
 
         uss.switching = false;
-        mHandler.removeMessages(REPORT_USER_SWITCH_COMPLETE_MSG);
-        mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_COMPLETE_MSG, newUserId, 0));
         stopGuestOrEphemeralUserIfBackground(oldUserId);
         stopUserOnSwitchIfEnforced(oldUserId);
         if (oldUserId == UserHandle.USER_SYSTEM) {
             // System user is never stopped, but its visibility is changed (as it is brought to the
             // background)
-            updateSystemUserVisibility(/* visible= */ false);
+            updateSystemUserVisibility(t, /* visible= */ false);
         }
 
         t.traceEnd(); // end continueUserSwitch
@@ -2183,21 +2198,22 @@
 
     @VisibleForTesting
     void completeUserSwitch(int newUserId) {
-        if (isUserSwitchUiEnabled()) {
-            // If there is no challenge set, dismiss the keyguard right away
-            if (!mInjector.getKeyguardManager().isDeviceSecure(newUserId)) {
-                // Wait until the keyguard is dismissed to unfreeze
-                mInjector.dismissKeyguard(
-                        new Runnable() {
-                            public void run() {
-                                unfreezeScreen();
-                            }
-                        },
-                        "User Switch");
-                return;
-            } else {
+        final boolean isUserSwitchUiEnabled = isUserSwitchUiEnabled();
+        final Runnable runnable = () -> {
+            if (isUserSwitchUiEnabled) {
                 unfreezeScreen();
             }
+            mHandler.removeMessages(REPORT_USER_SWITCH_COMPLETE_MSG);
+            mHandler.sendMessage(mHandler.obtainMessage(
+                    REPORT_USER_SWITCH_COMPLETE_MSG, newUserId, 0));
+        };
+
+        // If there is no challenge set, dismiss the keyguard right away
+        if (isUserSwitchUiEnabled && !mInjector.getKeyguardManager().isDeviceSecure(newUserId)) {
+            // Wait until the keyguard is dismissed to unfreeze
+            mInjector.dismissKeyguard(runnable, "User Switch");
+        } else {
+            runnable.run();
         }
     }
 
@@ -2526,16 +2542,22 @@
             Slogf.d(TAG, "onSystemReady()");
 
         }
-        updateCurrentProfileIds();
+        mInjector.getUserManagerInternal().addUserLifecycleListener(mUserLifecycleListener);
+        updateProfileRelatedCaches();
         mInjector.reportCurWakefulnessUsageEvent();
     }
 
     // TODO(b/242195409): remove this method if initial system user boot logic is refactored?
     void onSystemUserStarting() {
-        updateSystemUserVisibility(/* visible= */ !UserManager.isHeadlessSystemUserMode());
+        if (!UserManager.isHeadlessSystemUserMode()) {
+            // Don't need to call on HSUM because it will be called when the system user is
+            // restarted on background
+            mInjector.onUserStarting(UserHandle.USER_SYSTEM, /* visible= */ true);
+        }
     }
 
-    private void updateSystemUserVisibility(boolean visible) {
+    private void updateSystemUserVisibility(TimingsTraceAndSlog t, boolean visible) {
+        t.traceBegin("update-system-userVisibility-" + visible);
         if (DEBUG_MU) {
             Slogf.d(TAG, "updateSystemUserVisibility(): visible=%b", visible);
         }
@@ -2547,17 +2569,18 @@
                 mVisibleUsers.delete(userId);
             }
         }
-        mInjector.onUserStarting(userId, visible);
+        mInjector.notifySystemUserVisibilityChanged(visible);
+        t.traceEnd();
     }
 
     /**
-     * Refreshes the list of users related to the current user when either a
-     * user switch happens or when a new related user is started in the
-     * background.
+     * Refreshes the internal caches related to user profiles.
+     *
+     * <p>It's called every time a user is started.
      */
-    private void updateCurrentProfileIds() {
+    private void updateProfileRelatedCaches() {
         final List<UserInfo> profiles = mInjector.getUserManager().getProfiles(getCurrentUserId(),
-                false /* enabledOnly */);
+                /* enabledOnly= */ false);
         int[] currentProfileIds = new int[profiles.size()]; // profiles will not be null
         for (int i = 0; i < currentProfileIds.length; i++) {
             currentProfileIds[i] = profiles.get(i).id;
@@ -2824,6 +2847,18 @@
         }
     }
 
+    private void onUserAdded(UserInfo user) {
+        if (!user.isProfile()) {
+            return;
+        }
+        synchronized (mLock) {
+            if (user.profileGroupId == mCurrentUserId) {
+                mCurrentProfileIds = ArrayUtils.appendInt(mCurrentProfileIds, user.id);
+            }
+            mUserProfileGroupIds.put(user.id, user.profileGroupId);
+        }
+    }
+
     void onUserRemoved(@UserIdInt int userId) {
         synchronized (mLock) {
             int size = mUserProfileGroupIds.size();
@@ -2938,6 +2973,10 @@
             for (int i = 0; i < mVisibleUsers.size(); i++) {
                 proto.write(UserControllerProto.VISIBLE_USERS_ARRAY, mVisibleUsers.keyAt(i));
             }
+            proto.write(UserControllerProto.CURRENT_USER, mCurrentUserId);
+            for (int i = 0; i < mCurrentProfileIds.length; i++) {
+                proto.write(UserControllerProto.CURRENT_PROFILES, mCurrentProfileIds[i]);
+            }
             proto.end(token);
         }
     }
@@ -2975,6 +3014,7 @@
                     pw.println(mUserProfileGroupIds.valueAt(i));
                 }
             }
+            pw.println("  mCurrentProfileIds:" + Arrays.toString(mCurrentProfileIds));
             pw.println("  mCurrentUserId:" + mCurrentUserId);
             pw.println("  mTargetUserId:" + mTargetUserId);
             pw.println("  mLastActiveUsers:" + mLastActiveUsers);
@@ -3639,5 +3679,8 @@
             getSystemServiceManager().onUserStarting(TimingsTraceAndSlog.newAsyncLog(), userId,
                     visible);
         }
+        void notifySystemUserVisibilityChanged(boolean visible) {
+            getSystemServiceManager().onSystemUserVisibilityChanged(visible);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/audio/AudioDeviceBroker.java b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
index c59ee83..2fe06094 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceBroker.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceBroker.java
@@ -286,22 +286,9 @@
         if (AudioService.DEBUG_COMM_RTE) {
             Log.v(TAG, "setSpeakerphoneOn, on: " + on + " pid: " + pid);
         }
-
-        synchronized (mSetModeLock) {
-            synchronized (mDeviceStateLock) {
-                AudioDeviceAttributes device = null;
-                if (on) {
-                    device = new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, "");
-                } else {
-                    CommunicationRouteClient client = getCommunicationRouteClientForPid(pid);
-                    if (client == null || !client.requestsSpeakerphone()) {
-                        return;
-                    }
-                }
-                postSetCommunicationRouteForClient(new CommunicationClientInfo(
-                        cb, pid, device, BtHelper.SCO_MODE_UNDEFINED, eventSource));
-            }
-        }
+        postSetCommunicationDeviceForClient(new CommunicationDeviceInfo(
+                cb, pid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_SPEAKER, ""),
+                on, BtHelper.SCO_MODE_UNDEFINED, eventSource, false));
     }
 
     /**
@@ -311,6 +298,9 @@
      * @param device Device selected or null to unselect.
      * @param eventSource for logging purposes
      */
+
+    private static final long SET_COMMUNICATION_DEVICE_TIMEOUT_MS = 3000;
+
     /*package*/ boolean setCommunicationDevice(
             IBinder cb, int pid, AudioDeviceInfo device, String eventSource) {
 
@@ -318,21 +308,53 @@
             Log.v(TAG, "setCommunicationDevice, device: " + device + ", pid: " + pid);
         }
 
-        synchronized (mSetModeLock) {
-            synchronized (mDeviceStateLock) {
-                AudioDeviceAttributes deviceAttr = null;
-                if (device != null) {
-                    deviceAttr = new AudioDeviceAttributes(device);
-                } else {
-                    CommunicationRouteClient client = getCommunicationRouteClientForPid(pid);
-                    if (client == null) {
-                        return false;
+        AudioDeviceAttributes deviceAttr =
+                (device != null) ? new AudioDeviceAttributes(device) : null;
+        CommunicationDeviceInfo deviceInfo = new CommunicationDeviceInfo(cb, pid, deviceAttr,
+                device != null, BtHelper.SCO_MODE_UNDEFINED, eventSource, true);
+        postSetCommunicationDeviceForClient(deviceInfo);
+        boolean status;
+        synchronized (deviceInfo) {
+            final long start = System.currentTimeMillis();
+            long elapsed = 0;
+            while (deviceInfo.mWaitForStatus) {
+                try {
+                    deviceInfo.wait(SET_COMMUNICATION_DEVICE_TIMEOUT_MS - elapsed);
+                } catch (InterruptedException e) {
+                    elapsed = System.currentTimeMillis() - start;
+                    if (elapsed >= SET_COMMUNICATION_DEVICE_TIMEOUT_MS) {
+                        deviceInfo.mStatus = false;
+                        deviceInfo.mWaitForStatus = false;
                     }
                 }
-                postSetCommunicationRouteForClient(new CommunicationClientInfo(
-                        cb, pid, deviceAttr, BtHelper.SCO_MODE_UNDEFINED, eventSource));
+            }
+            status = deviceInfo.mStatus;
+        }
+        return status;
+    }
+
+    /**
+     * Sets or resets the communication device for matching client. If no client matches and the
+     * request is to reset for a given device (deviceInfo.mOn == false), the method is a noop.
+     * @param deviceInfo information on the device and requester {@link #CommunicationDeviceInfo}
+     * @return true if the communication device is set or reset
+     */
+    @GuardedBy("mDeviceStateLock")
+    /*package*/ boolean onSetCommunicationDeviceForClient(CommunicationDeviceInfo deviceInfo) {
+        if (AudioService.DEBUG_COMM_RTE) {
+            Log.v(TAG, "onSetCommunicationDeviceForClient: " + deviceInfo);
+        }
+        if (!deviceInfo.mOn) {
+            CommunicationRouteClient client = getCommunicationRouteClientForPid(deviceInfo.mPid);
+            if (client == null || (deviceInfo.mDevice != null
+                    && !deviceInfo.mDevice.equals(client.getDevice()))) {
+                return false;
             }
         }
+
+        AudioDeviceAttributes device = deviceInfo.mOn ? deviceInfo.mDevice : null;
+        setCommunicationRouteForClient(deviceInfo.mCb, deviceInfo.mPid, device,
+                deviceInfo.mScoAudioMode, deviceInfo.mEventSource);
         return true;
     }
 
@@ -344,7 +366,7 @@
         if (AudioService.DEBUG_COMM_RTE) {
             Log.v(TAG, "setCommunicationRouteForClient: device: " + device);
         }
-        AudioService.sDeviceLogger.log((new EventLogger.StringEvent(
+        AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
                                         "setCommunicationRouteForClient for pid: " + pid
                                         + " device: " + device
                                         + " from API: " + eventSource)).printLog(TAG));
@@ -390,7 +412,7 @@
             mBtHelper.stopBluetoothSco(eventSource);
         }
 
-        sendLMsgNoDelay(MSG_L_UPDATE_COMMUNICATION_ROUTE, SENDMSG_QUEUE, eventSource);
+        updateCommunicationRoute(eventSource);
     }
 
     /**
@@ -424,7 +446,7 @@
         CommunicationRouteClient crc = topCommunicationRouteClient();
         AudioDeviceAttributes device = crc != null ? crc.getDevice() : null;
         if (AudioService.DEBUG_COMM_RTE) {
-            Log.v(TAG, "requestedCommunicationDevice, device: "
+            Log.v(TAG, "requestedCommunicationDevice: "
                     + device + " mAudioModeOwner: " + mAudioModeOwner.toString());
         }
         return device;
@@ -822,37 +844,22 @@
                 @NonNull String eventSource) {
 
         if (AudioService.DEBUG_COMM_RTE) {
-            Log.v(TAG, "startBluetoothScoForClient_Sync, pid: " + pid);
+            Log.v(TAG, "startBluetoothScoForClient, pid: " + pid);
         }
-
-        synchronized (mSetModeLock) {
-            synchronized (mDeviceStateLock) {
-                AudioDeviceAttributes device =
-                        new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, "");
-
-                postSetCommunicationRouteForClient(new CommunicationClientInfo(
-                        cb, pid, device, scoAudioMode, eventSource));
-            }
-        }
+        postSetCommunicationDeviceForClient(new CommunicationDeviceInfo(
+                cb, pid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, ""),
+                true, scoAudioMode, eventSource, false));
     }
 
     /*package*/ void stopBluetoothScoForClient(
                         IBinder cb, int pid, @NonNull String eventSource) {
 
         if (AudioService.DEBUG_COMM_RTE) {
-            Log.v(TAG, "stopBluetoothScoForClient_Sync, pid: " + pid);
+            Log.v(TAG, "stopBluetoothScoForClient, pid: " + pid);
         }
-
-        synchronized (mSetModeLock) {
-            synchronized (mDeviceStateLock) {
-                CommunicationRouteClient client = getCommunicationRouteClientForPid(pid);
-                if (client == null || !client.requestsBluetoothSco()) {
-                    return;
-                }
-                postSetCommunicationRouteForClient(new CommunicationClientInfo(
-                        cb, pid, null, BtHelper.SCO_MODE_UNDEFINED, eventSource));
-            }
-        }
+        postSetCommunicationDeviceForClient(new CommunicationDeviceInfo(
+                cb, pid, new AudioDeviceAttributes(AudioSystem.DEVICE_OUT_BLUETOOTH_SCO, ""),
+                false, BtHelper.SCO_MODE_UNDEFINED, eventSource, false));
     }
 
     /*package*/ int setPreferredDevicesForStrategySync(int strategy,
@@ -990,7 +997,8 @@
     }
 
     //---------------------------------------------------------------------
-    // Message handling on behalf of helper classes
+    // Message handling on behalf of helper classes.
+    // Each of these methods posts a message to mBrokerHandler message queue.
     /*package*/ void postBroadcastScoConnectionState(int state) {
         sendIMsgNoDelay(MSG_I_BROADCAST_BT_CONNECTION_STATE, SENDMSG_QUEUE, state);
     }
@@ -1046,28 +1054,34 @@
         sendLMsgNoDelay(MSG_L_UPDATE_COMMUNICATION_ROUTE_CLIENT, SENDMSG_QUEUE, eventSource);
     }
 
-    /*package*/ void postSetCommunicationRouteForClient(CommunicationClientInfo info) {
-        sendLMsgNoDelay(MSG_L_SET_COMMUNICATION_ROUTE_FOR_CLIENT, SENDMSG_QUEUE, info);
+    /*package*/ void postSetCommunicationDeviceForClient(CommunicationDeviceInfo info) {
+        sendLMsgNoDelay(MSG_L_SET_COMMUNICATION_DEVICE_FOR_CLIENT, SENDMSG_QUEUE, info);
     }
 
     /*package*/ void postScoAudioStateChanged(int state) {
         sendIMsgNoDelay(MSG_I_SCO_AUDIO_STATE_CHANGED, SENDMSG_QUEUE, state);
     }
 
-    /*package*/ static final class CommunicationClientInfo {
-        final @NonNull IBinder mCb;
-        final int mPid;
-        final @NonNull AudioDeviceAttributes mDevice;
-        final int mScoAudioMode;
-        final @NonNull String mEventSource;
+    /*package*/ static final class CommunicationDeviceInfo {
+        final @NonNull IBinder mCb; // Identifies the requesting client for death handler
+        final int mPid; // Requester process ID
+        final @Nullable AudioDeviceAttributes mDevice; // Device being set or reset.
+        final boolean mOn; // true if setting, false if resetting
+        final int mScoAudioMode; // only used for SCO: requested audio mode
+        final @NonNull String mEventSource; // caller identifier for logging
+        boolean mWaitForStatus; // true if the caller waits for a completion status (API dependent)
+        boolean mStatus = false; // completion status only used if mWaitForStatus is true
 
-        CommunicationClientInfo(@NonNull IBinder cb, int pid, @NonNull AudioDeviceAttributes device,
-                int scoAudioMode, @NonNull String eventSource) {
+        CommunicationDeviceInfo(@NonNull IBinder cb, int pid,
+                @Nullable AudioDeviceAttributes device, boolean on, int scoAudioMode,
+                @NonNull String eventSource, boolean waitForStatus) {
             mCb = cb;
             mPid = pid;
             mDevice = device;
+            mOn = on;
             mScoAudioMode = scoAudioMode;
             mEventSource = eventSource;
+            mWaitForStatus = waitForStatus;
         }
 
         // redefine equality op so we can match messages intended for this client
@@ -1079,21 +1093,24 @@
             if (this == o) {
                 return true;
             }
-            if (!(o instanceof CommunicationClientInfo)) {
+            if (!(o instanceof CommunicationDeviceInfo)) {
                 return false;
             }
 
-            return mCb.equals(((CommunicationClientInfo) o).mCb)
-                    && mPid == ((CommunicationClientInfo) o).mPid;
+            return mCb.equals(((CommunicationDeviceInfo) o).mCb)
+                    && mPid == ((CommunicationDeviceInfo) o).mPid;
         }
 
         @Override
         public String toString() {
-            return "CommunicationClientInfo mCb=" + mCb.toString()
-                    +"mPid=" + mPid
-                    +"mDevice=" + mDevice.toString()
-                    +"mScoAudioMode=" + mScoAudioMode
-                    +"mEventSource=" + mEventSource;
+            return "CommunicationDeviceInfo mCb=" + mCb.toString()
+                    + " mPid=" + mPid
+                    + " mDevice=[" + (mDevice != null ? mDevice.toString() : "null") + "]"
+                    + " mOn=" + mOn
+                    + " mScoAudioMode=" + mScoAudioMode
+                    + " mEventSource=" + mEventSource
+                    + " mWaitForStatus=" + mWaitForStatus
+                    + " mStatus=" + mStatus;
         }
     }
 
@@ -1212,7 +1229,7 @@
         if (useCase == AudioSystem.FOR_MEDIA) {
             postReportNewRoutes(fromA2dp);
         }
-        AudioService.sForceUseLogger.log(
+        AudioService.sForceUseLogger.enqueue(
                 new AudioServiceEvents.ForceUseEvent(useCase, config, eventSource));
         new MediaMetrics.Item(MediaMetrics.Name.AUDIO_FORCE_USE + MediaMetrics.SEPARATOR
                 + AudioSystem.forceUseUsageToString(useCase))
@@ -1230,7 +1247,7 @@
     }
 
     private void onSendBecomingNoisyIntent() {
-        AudioService.sDeviceLogger.log((new EventLogger.StringEvent(
+        AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
                 "broadcast ACTION_AUDIO_BECOMING_NOISY")).printLog(TAG));
         mSystemServer.sendDeviceBecomingNoisyIntent();
     }
@@ -1297,7 +1314,7 @@
                             updateActiveCommunicationDevice();
                             mDeviceInventory.onRestoreDevices();
                             mBtHelper.onAudioServerDiedRestoreA2dp();
-                            onUpdateCommunicationRoute("MSG_RESTORE_DEVICES");
+                            updateCommunicationRoute("MSG_RESTORE_DEVICES");
                         }
                     }
                     break;
@@ -1392,12 +1409,19 @@
                     }
                     break;
 
-                case MSG_L_SET_COMMUNICATION_ROUTE_FOR_CLIENT:
+                case MSG_L_SET_COMMUNICATION_DEVICE_FOR_CLIENT:
+                    CommunicationDeviceInfo deviceInfo = (CommunicationDeviceInfo) msg.obj;
+                    boolean status;
                     synchronized (mSetModeLock) {
                         synchronized (mDeviceStateLock) {
-                            CommunicationClientInfo info = (CommunicationClientInfo) msg.obj;
-                            setCommunicationRouteForClient(info.mCb, info.mPid, info.mDevice,
-                                    info.mScoAudioMode, info.mEventSource);
+                            status = onSetCommunicationDeviceForClient(deviceInfo);
+                        }
+                    }
+                    synchronized (deviceInfo) {
+                        if (deviceInfo.mWaitForStatus) {
+                            deviceInfo.mStatus = status;
+                            deviceInfo.mWaitForStatus = false;
+                            deviceInfo.notify();
                         }
                     }
                     break;
@@ -1410,14 +1434,6 @@
                     }
                     break;
 
-                case MSG_L_UPDATE_COMMUNICATION_ROUTE:
-                    synchronized (mSetModeLock) {
-                        synchronized (mDeviceStateLock) {
-                            onUpdateCommunicationRoute((String) msg.obj);
-                        }
-                    }
-                    break;
-
                 case MSG_L_COMMUNICATION_ROUTE_CLIENT_DIED:
                     synchronized (mSetModeLock) {
                         synchronized (mDeviceStateLock) {
@@ -1468,7 +1484,7 @@
                 case MSG_L_BT_ACTIVE_DEVICE_CHANGE_EXT: {
                     final BtDeviceInfo info = (BtDeviceInfo) msg.obj;
                     if (info.mDevice == null) break;
-                    AudioService.sDeviceLogger.log((new EventLogger.StringEvent(
+                    AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
                             "msg: onBluetoothActiveDeviceChange "
                                     + " state=" + info.mState
                                     // only querying address as this is the only readily available
@@ -1568,8 +1584,7 @@
     private static final int MSG_IL_SAVE_PREF_DEVICES_FOR_CAPTURE_PRESET = 37;
     private static final int MSG_I_SAVE_CLEAR_PREF_DEVICES_FOR_CAPTURE_PRESET = 38;
 
-    private static final int MSG_L_UPDATE_COMMUNICATION_ROUTE = 39;
-    private static final int MSG_L_SET_COMMUNICATION_ROUTE_FOR_CLIENT = 42;
+    private static final int MSG_L_SET_COMMUNICATION_DEVICE_FOR_CLIENT = 42;
     private static final int MSG_L_UPDATE_COMMUNICATION_ROUTE_CLIENT = 43;
     private static final int MSG_I_SCO_AUDIO_STATE_CHANGED = 44;
 
@@ -1793,18 +1808,6 @@
         AudioDeviceAttributes getDevice() {
             return mDevice;
         }
-
-        boolean requestsBluetoothSco() {
-            return mDevice != null
-                    && mDevice.getType()
-                        == AudioDeviceInfo.TYPE_BLUETOOTH_SCO;
-        }
-
-        boolean requestsSpeakerphone() {
-            return mDevice != null
-                    && mDevice.getType()
-                        == AudioDeviceInfo.TYPE_BUILTIN_SPEAKER;
-        }
     }
 
     // @GuardedBy("mSetModeLock")
@@ -1852,14 +1855,14 @@
      */
     // @GuardedBy("mSetModeLock")
     @GuardedBy("mDeviceStateLock")
-    private void onUpdateCommunicationRoute(String eventSource) {
+    private void updateCommunicationRoute(String eventSource) {
         AudioDeviceAttributes preferredCommunicationDevice = preferredCommunicationDevice();
         if (AudioService.DEBUG_COMM_RTE) {
-            Log.v(TAG, "onUpdateCommunicationRoute, preferredCommunicationDevice: "
+            Log.v(TAG, "updateCommunicationRoute, preferredCommunicationDevice: "
                     + preferredCommunicationDevice + " eventSource: " + eventSource);
         }
-        AudioService.sDeviceLogger.log((new EventLogger.StringEvent(
-                "onUpdateCommunicationRoute, preferredCommunicationDevice: "
+        AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
+                "updateCommunicationRoute, preferredCommunicationDevice: "
                 + preferredCommunicationDevice + " eventSource: " + eventSource)));
 
         if (preferredCommunicationDevice == null
@@ -1895,7 +1898,7 @@
     // @GuardedBy("mSetModeLock")
     @GuardedBy("mDeviceStateLock")
     private void onUpdateCommunicationRouteClient(String eventSource) {
-        onUpdateCommunicationRoute(eventSource);
+        updateCommunicationRoute(eventSource);
         CommunicationRouteClient crc = topCommunicationRouteClient();
         if (AudioService.DEBUG_COMM_RTE) {
             Log.v(TAG, "onUpdateCommunicationRouteClient, crc: "
diff --git a/services/core/java/com/android/server/audio/AudioDeviceInventory.java b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
index ce36ff8..c8f282f 100644
--- a/services/core/java/com/android/server/audio/AudioDeviceInventory.java
+++ b/services/core/java/com/android/server/audio/AudioDeviceInventory.java
@@ -309,7 +309,7 @@
             address = "";
         }
 
-        AudioService.sDeviceLogger.log(new EventLogger.StringEvent("BT connected:"
+        AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent("BT connected:"
                         + " addr=" + address
                         + " profile=" + btInfo.mProfile
                         + " state=" + btInfo.mState
@@ -412,13 +412,13 @@
         if (!BluetoothAdapter.checkBluetoothAddress(address)) {
             address = "";
         }
-        AudioService.sDeviceLogger.log(new EventLogger.StringEvent(
+        AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
                 "onBluetoothA2dpDeviceConfigChange addr=" + address
                     + " event=" + BtHelper.a2dpDeviceEventToString(event)));
 
         synchronized (mDevicesLock) {
             if (mDeviceBroker.hasScheduledA2dpConnection(btDevice)) {
-                AudioService.sDeviceLogger.log(new EventLogger.StringEvent(
+                AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
                         "A2dp config change ignored (scheduled connection change)")
                         .printLog(TAG));
                 mmi.set(MediaMetrics.Property.EARLY_RETURN, "A2dp config change ignored")
@@ -460,7 +460,7 @@
                     BtHelper.getName(btDevice), a2dpCodec);
 
             if (res != AudioSystem.AUDIO_STATUS_OK) {
-                AudioService.sDeviceLogger.log(new EventLogger.StringEvent(
+                AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
                         "APM handleDeviceConfigChange failed for A2DP device addr=" + address
                                 + " codec=" + AudioSystem.audioFormatToString(a2dpCodec))
                         .printLog(TAG));
@@ -472,7 +472,7 @@
                                 BluetoothProfile.A2DP, BluetoothProfile.STATE_DISCONNECTED,
                                 musicDevice, AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP));
             } else {
-                AudioService.sDeviceLogger.log(new EventLogger.StringEvent(
+                AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
                         "APM handleDeviceConfigChange success for A2DP device addr=" + address
                                 + " codec=" + AudioSystem.audioFormatToString(a2dpCodec))
                         .printLog(TAG));
@@ -522,7 +522,7 @@
                             AudioDeviceInventory.WiredDeviceConnectionState wdcs) {
         int type = wdcs.mAttributes.getInternalType();
 
-        AudioService.sDeviceLogger.log(new AudioServiceEvents.WiredDevConnectEvent(wdcs));
+        AudioService.sDeviceLogger.enqueue(new AudioServiceEvents.WiredDevConnectEvent(wdcs));
 
         MediaMetrics.Item mmi = new MediaMetrics.Item(mMetricsId
                 + "onSetWiredDeviceConnectionState")
@@ -619,7 +619,7 @@
             @NonNull List<AudioDeviceAttributes> devices) {
         final long identity = Binder.clearCallingIdentity();
 
-        AudioService.sDeviceLogger.log((new EventLogger.StringEvent(
+        AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
                                 "setPreferredDevicesForStrategySync, strategy: " + strategy
                                 + " devices: " + devices)).printLog(TAG));
         final int status = mAudioSystem.setDevicesRoleForStrategy(
@@ -635,7 +635,7 @@
     /*package*/ int removePreferredDevicesForStrategySync(int strategy) {
         final long identity = Binder.clearCallingIdentity();
 
-        AudioService.sDeviceLogger.log((new EventLogger.StringEvent(
+        AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
                 "removePreferredDevicesForStrategySync, strategy: "
                 + strategy)).printLog(TAG));
 
@@ -1000,13 +1000,13 @@
         // TODO: log in MediaMetrics once distinction between connection failure and
         // double connection is made.
         if (res != AudioSystem.AUDIO_STATUS_OK) {
-            AudioService.sDeviceLogger.log(new EventLogger.StringEvent(
+            AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
                     "APM failed to make available A2DP device addr=" + address
                             + " error=" + res).printLog(TAG));
             // TODO: connection failed, stop here
             // TODO: return;
         } else {
-            AudioService.sDeviceLogger.log(new EventLogger.StringEvent(
+            AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
                     "A2DP device addr=" + address + " now available").printLog(TAG));
         }
 
@@ -1047,7 +1047,7 @@
         if (!deviceToRemoveKey
                 .equals(mApmConnectedDevices.get(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP))) {
             // removing A2DP device not currently used by AudioPolicy, log but don't act on it
-            AudioService.sDeviceLogger.log((new EventLogger.StringEvent(
+            AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
                     "A2DP device " + address + " made unavailable, was not used")).printLog(TAG));
             mmi.set(MediaMetrics.Property.EARLY_RETURN,
                     "A2DP device made unavailable, was not used")
@@ -1062,13 +1062,13 @@
                 AudioSystem.DEVICE_STATE_UNAVAILABLE, a2dpCodec);
 
         if (res != AudioSystem.AUDIO_STATUS_OK) {
-            AudioService.sDeviceLogger.log(new EventLogger.StringEvent(
+            AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
                     "APM failed to make unavailable A2DP device addr=" + address
                             + " error=" + res).printLog(TAG));
             // TODO:  failed to disconnect, stop here
             // TODO: return;
         } else {
-            AudioService.sDeviceLogger.log((new EventLogger.StringEvent(
+            AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
                     "A2DP device addr=" + address + " made unavailable")).printLog(TAG));
         }
         mApmConnectedDevices.remove(AudioSystem.DEVICE_OUT_BLUETOOTH_A2DP);
@@ -1314,7 +1314,7 @@
                     && !mDeviceBroker.hasAudioFocusUsers()) {
                 // no media playback, not a "becoming noisy" situation, otherwise it could cause
                 // the pausing of some apps that are playing remotely
-                AudioService.sDeviceLogger.log((new EventLogger.StringEvent(
+                AudioService.sDeviceLogger.enqueue((new EventLogger.StringEvent(
                         "dropping ACTION_AUDIO_BECOMING_NOISY")).printLog(TAG));
                 mmi.set(MediaMetrics.Property.DELAY_MS, 0).record(); // OK to return
                 return 0;
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 81572b4..4fda233 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -41,6 +41,7 @@
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
+import android.app.ActivityThread;
 import android.app.AlarmManager;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
@@ -153,6 +154,7 @@
 import android.os.VibrationEffect;
 import android.os.Vibrator;
 import android.os.VibratorManager;
+import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.provider.Settings.System;
 import android.service.notification.ZenModeConfig;
@@ -174,6 +176,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.Preconditions;
 import com.android.server.EventLogTags;
@@ -233,6 +236,7 @@
             AudioSystemAdapter.OnVolRangeInitRequestListener {
 
     private static final String TAG = "AS.AudioService";
+    private static final boolean CONFIG_DEFAULT_VAL = false;
 
     private final AudioSystemAdapter mAudioSystem;
     private final SystemServerAdapter mSystemServer;
@@ -987,10 +991,11 @@
      * @param looper Looper to use for the service's message handler. If this is null, an
      *               {@link AudioSystemThread} is created as the messaging thread instead.
      */
+    @RequiresPermission(Manifest.permission.READ_DEVICE_CONFIG)
     public AudioService(Context context, AudioSystemAdapter audioSystem,
             SystemServerAdapter systemServer, SettingsAdapter settings, @Nullable Looper looper,
             AppOpsManager appOps) {
-        sLifecycleLogger.log(new EventLogger.StringEvent("AudioService()"));
+        sLifecycleLogger.enqueue(new EventLogger.StringEvent("AudioService()"));
         mContext = context;
         mContentResolver = context.getContentResolver();
         mAppOps = appOps;
@@ -1026,8 +1031,12 @@
         mUseVolumeGroupAliases = mContext.getResources().getBoolean(
                 com.android.internal.R.bool.config_handleVolumeAliasesUsingVolumeGroups);
 
-        mNotifAliasRing = mContext.getResources().getBoolean(
-                com.android.internal.R.bool.config_alias_ring_notif_stream_types);
+        mNotifAliasRing = !DeviceConfig.getBoolean(DeviceConfig.NAMESPACE_SYSTEMUI,
+                SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, false);
+
+        DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_SYSTEMUI,
+                ActivityThread.currentApplication().getMainExecutor(),
+                this::onDeviceConfigChange);
 
         // Initialize volume
         // Priority 1 - Android Property
@@ -1246,6 +1255,22 @@
     }
 
     /**
+     * Separating notification volume from ring is NOT of aliasing the corresponding streams
+     * @param properties
+     */
+    private void onDeviceConfigChange(DeviceConfig.Properties properties) {
+        Set<String> changeSet = properties.getKeyset();
+        if (changeSet.contains(SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION)) {
+            boolean newNotifAliasRing = !properties.getBoolean(
+                    SystemUiDeviceConfigFlags.VOLUME_SEPARATE_NOTIFICATION, CONFIG_DEFAULT_VAL);
+            if (mNotifAliasRing != newNotifAliasRing) {
+                mNotifAliasRing = newNotifAliasRing;
+                updateStreamVolumeAlias(true, TAG);
+            }
+        }
+    }
+
+    /**
      * Called by handling of MSG_INIT_STREAMS_VOLUMES
      */
     private void onInitStreamsAndVolumes() {
@@ -1539,14 +1564,14 @@
         if (!mSystemReady ||
                 (AudioSystem.checkAudioFlinger() != AudioSystem.AUDIO_STATUS_OK)) {
             Log.e(TAG, "Audioserver died.");
-            sLifecycleLogger.log(new EventLogger.StringEvent(
+            sLifecycleLogger.enqueue(new EventLogger.StringEvent(
                     "onAudioServerDied() audioserver died"));
             sendMsg(mAudioHandler, MSG_AUDIO_SERVER_DIED, SENDMSG_NOOP, 0, 0,
                     null, 500);
             return;
         }
         Log.i(TAG, "Audioserver started.");
-        sLifecycleLogger.log(new EventLogger.StringEvent(
+        sLifecycleLogger.enqueue(new EventLogger.StringEvent(
                 "onAudioServerDied() audioserver started"));
 
         updateAudioHalPids();
@@ -1776,7 +1801,7 @@
 
         // did it work? check based on status
         if (status != AudioSystem.AUDIO_STATUS_OK) {
-            sLifecycleLogger.log(new EventLogger.StringEvent(
+            sLifecycleLogger.enqueue(new EventLogger.StringEvent(
                     caller + ": initStreamVolume failed with " + status + " will retry")
                     .printLog(ALOGE, TAG));
             sendMsg(mAudioHandler, MSG_REINIT_VOLUMES, SENDMSG_NOOP, 0, 0,
@@ -1790,7 +1815,7 @@
         }
 
         // success
-        sLifecycleLogger.log(new EventLogger.StringEvent(
+        sLifecycleLogger.enqueue(new EventLogger.StringEvent(
                 caller + ": initStreamVolume succeeded").printLog(ALOGI, TAG));
     }
 
@@ -1813,7 +1838,7 @@
             }
         }
         if (!success) {
-            sLifecycleLogger.log(new EventLogger.StringEvent(
+            sLifecycleLogger.enqueue(new EventLogger.StringEvent(
                     caller + ": initStreamVolume succeeded but invalid mix/max levels, will retry")
                     .printLog(ALOGW, TAG));
             sendMsg(mAudioHandler, MSG_REINIT_VOLUMES, SENDMSG_NOOP, 0, 0,
@@ -2772,7 +2797,7 @@
                 "setPreferredDeviceForStrategy u/pid:%d/%d strat:%d dev:%s",
                 Binder.getCallingUid(), Binder.getCallingPid(), strategy,
                 devices.stream().map(e -> e.toString()).collect(Collectors.joining(",")));
-        sDeviceLogger.log(new EventLogger.StringEvent(logString).printLog(TAG));
+        sDeviceLogger.enqueue(new EventLogger.StringEvent(logString).printLog(TAG));
         if (devices.stream().anyMatch(device ->
                 device.getRole() == AudioDeviceAttributes.ROLE_INPUT)) {
             Log.e(TAG, "Unsupported input routing in " + logString);
@@ -2794,7 +2819,7 @@
 
         final String logString =
                 String.format("removePreferredDeviceForStrategy strat:%d", strategy);
-        sDeviceLogger.log(new EventLogger.StringEvent(logString).printLog(TAG));
+        sDeviceLogger.enqueue(new EventLogger.StringEvent(logString).printLog(TAG));
 
         final int status = mDeviceBroker.removePreferredDevicesForStrategySync(strategy);
         if (status != AudioSystem.SUCCESS) {
@@ -2862,7 +2887,7 @@
                 "setPreferredDevicesForCapturePreset u/pid:%d/%d source:%d dev:%s",
                 Binder.getCallingUid(), Binder.getCallingPid(), capturePreset,
                 devices.stream().map(e -> e.toString()).collect(Collectors.joining(",")));
-        sDeviceLogger.log(new EventLogger.StringEvent(logString).printLog(TAG));
+        sDeviceLogger.enqueue(new EventLogger.StringEvent(logString).printLog(TAG));
         if (devices.stream().anyMatch(device ->
                 device.getRole() == AudioDeviceAttributes.ROLE_OUTPUT)) {
             Log.e(TAG, "Unsupported output routing in " + logString);
@@ -2885,7 +2910,7 @@
 
         final String logString = String.format(
                 "removePreferredDeviceForCapturePreset source:%d", capturePreset);
-        sDeviceLogger.log(new EventLogger.StringEvent(logString).printLog(TAG));
+        sDeviceLogger.enqueue(new EventLogger.StringEvent(logString).printLog(TAG));
 
         final int status = mDeviceBroker.clearPreferredDevicesForCapturePresetSync(capturePreset);
         if (status != AudioSystem.SUCCESS) {
@@ -3059,9 +3084,10 @@
                 + ", volControlStream=" + mVolumeControlStream
                 + ", userSelect=" + mUserSelectedVolumeControlStream);
         if (direction != AudioManager.ADJUST_SAME) {
-            sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_SUGG_VOL, suggestedStreamType,
-                    direction/*val1*/, flags/*val2*/, new StringBuilder(callingPackage)
-                    .append("/").append(caller).append(" uid:").append(uid).toString()));
+            sVolumeLogger.enqueue(
+                    new VolumeEvent(VolumeEvent.VOL_ADJUST_SUGG_VOL, suggestedStreamType,
+                            direction/*val1*/, flags/*val2*/, new StringBuilder(callingPackage)
+                            .append("/").append(caller).append(" uid:").append(uid).toString()));
         }
 
         boolean hasExternalVolumeController = notifyExternalVolumeController(direction);
@@ -3160,7 +3186,7 @@
             return;
         }
 
-        sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_STREAM_VOL, streamType,
+        sVolumeLogger.enqueue(new VolumeEvent(VolumeEvent.VOL_ADJUST_STREAM_VOL, streamType,
                 direction/*val1*/, flags/*val2*/, callingPackage));
         adjustStreamVolume(streamType, direction, flags, callingPackage, callingPackage,
                 Binder.getCallingUid(), Binder.getCallingPid(), attributionTag,
@@ -3643,7 +3669,7 @@
         }
         final VolumeGroupState vgs = sVolumeGroupStates.get(volumeGroup);
 
-        sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_SET_GROUP_VOL, attr, vgs.name(),
+        sVolumeLogger.enqueue(new VolumeEvent(VolumeEvent.VOL_SET_GROUP_VOL, attr, vgs.name(),
                 index/*val1*/, flags/*val2*/, callingPackage));
 
         vgs.setVolumeIndex(index, flags);
@@ -3800,7 +3826,7 @@
                 ? new VolumeEvent(VolumeEvent.VOL_SET_STREAM_VOL, streamType,
                     index/*val1*/, flags/*val2*/, callingPackage)
                 : new DeviceVolumeEvent(streamType, index, device, callingPackage);
-        sVolumeLogger.log(event);
+        sVolumeLogger.enqueue(event);
         setStreamVolume(streamType, index, flags, device,
                 callingPackage, callingPackage, attributionTag,
                 Binder.getCallingUid(), callingOrSelfHasAudioSettingsPermission());
@@ -4003,7 +4029,7 @@
     private void updateHearingAidVolumeOnVoiceActivityUpdate() {
         final int streamType = getBluetoothContextualVolumeStream();
         final int index = getStreamVolume(streamType);
-        sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_VOICE_ACTIVITY_HEARING_AID,
+        sVolumeLogger.enqueue(new VolumeEvent(VolumeEvent.VOL_VOICE_ACTIVITY_HEARING_AID,
                 mVoicePlaybackActive.get(), streamType, index));
         mDeviceBroker.postSetHearingAidVolumeIndex(index * 10, streamType);
 
@@ -4044,7 +4070,7 @@
         if (AudioSystem.isSingleAudioDeviceType(
                 absVolumeMultiModeCaseDevices, AudioSystem.DEVICE_OUT_HEARING_AID)) {
             final int index = getStreamVolume(streamType);
-            sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_MODE_CHANGE_HEARING_AID,
+            sVolumeLogger.enqueue(new VolumeEvent(VolumeEvent.VOL_MODE_CHANGE_HEARING_AID,
                     newMode, streamType, index));
             mDeviceBroker.postSetHearingAidVolumeIndex(index * 10, streamType);
         }
@@ -5449,7 +5475,7 @@
                         /*obj*/ null, /*delay*/ 0);
                 int previousMode = mMode.getAndSet(mode);
                 // Note: newModeOwnerPid is always 0 when actualMode is MODE_NORMAL
-                mModeLogger.log(new PhoneStateEvent(requesterPackage, requesterPid,
+                mModeLogger.enqueue(new PhoneStateEvent(requesterPackage, requesterPid,
                         requestedMode, pid, mode));
 
                 int streamType = getActiveStreamType(AudioManager.USE_DEFAULT_STREAM_TYPE);
@@ -5594,7 +5620,7 @@
         }
 
         if (direction != AudioManager.ADJUST_SAME) {
-            sVolumeLogger.log(new VolumeEvent(VolumeEvent.VOL_ADJUST_VOL_UID, streamType,
+            sVolumeLogger.enqueue(new VolumeEvent(VolumeEvent.VOL_ADJUST_VOL_UID, streamType,
                     direction/*val1*/, flags/*val2*/,
                     new StringBuilder(packageName).append(" uid:").append(uid)
                     .toString()));
@@ -6926,7 +6952,7 @@
 
         Objects.requireNonNull(device);
         AudioManager.enforceValidVolumeBehavior(deviceVolumeBehavior);
-        sVolumeLogger.log(new EventLogger.StringEvent("setDeviceVolumeBehavior: dev:"
+        sVolumeLogger.enqueue(new EventLogger.StringEvent("setDeviceVolumeBehavior: dev:"
                 + AudioSystem.getOutputDeviceName(device.getInternalType()) + " addr:"
                 + device.getAddress() + " behavior:"
                 + AudioDeviceVolumeManager.volumeBehaviorName(deviceVolumeBehavior)
@@ -6982,7 +7008,7 @@
         }
 
         // log event and caller
-        sDeviceLogger.log(new EventLogger.StringEvent(
+        sDeviceLogger.enqueue(new EventLogger.StringEvent(
                 "Volume behavior " + deviceVolumeBehavior + " for dev=0x"
                       + Integer.toHexString(audioSystemDeviceOut) + " from:" + caller));
         // make sure we have a volume entry for this device, and that volume is updated according
@@ -7630,7 +7656,7 @@
             final int status = AudioSystem.initStreamVolume(
                     streamType, mIndexMin / 10, mIndexMax / 10);
             if (status != AudioSystem.AUDIO_STATUS_OK) {
-                sLifecycleLogger.log(new EventLogger.StringEvent(
+                sLifecycleLogger.enqueue(new EventLogger.StringEvent(
                          "VSS() stream:" + streamType + " initStreamVolume=" + status)
                         .printLog(ALOGE, TAG));
                 sendMsg(mAudioHandler, MSG_REINIT_VOLUMES, SENDMSG_NOOP, 0, 0,
@@ -8049,7 +8075,7 @@
                 }
             }
             if (changed) {
-                sVolumeLogger.log(new VolumeEvent(
+                sVolumeLogger.enqueue(new VolumeEvent(
                         VolumeEvent.VOL_MUTE_STREAM_INT, mStreamType, state));
             }
             return changed;
@@ -8219,10 +8245,10 @@
             streamState.setIndex(index, update.mDevice, update.mCaller,
                     // trusted as index is always validated before message is posted
                     true /*hasModifyAudioSettings*/);
-            sVolumeLogger.log(new EventLogger.StringEvent(update.mCaller + " dev:0x"
+            sVolumeLogger.enqueue(new EventLogger.StringEvent(update.mCaller + " dev:0x"
                     + Integer.toHexString(update.mDevice) + " volIdx:" + index));
         } else {
-            sVolumeLogger.log(new EventLogger.StringEvent(update.mCaller
+            sVolumeLogger.enqueue(new EventLogger.StringEvent(update.mCaller
                     + " update vol on dev:0x" + Integer.toHexString(update.mDevice)));
         }
         setDeviceVolume(streamState, update.mDevice);
@@ -8400,7 +8426,7 @@
                             .set(MediaMetrics.Property.FORCE_USE_MODE,
                                     AudioSystem.forceUseConfigToString(config))
                             .record();
-                    sForceUseLogger.log(
+                    sForceUseLogger.enqueue(
                             new AudioServiceEvents.ForceUseEvent(useCase, config, eventSource));
                     mAudioSystem.setForceUse(useCase, config);
                 }
@@ -8669,7 +8695,7 @@
 
     private void avrcpSupportsAbsoluteVolume(String address, boolean support) {
         // address is not used for now, but may be used when multiple a2dp devices are supported
-        sVolumeLogger.log(new EventLogger.StringEvent("avrcpSupportsAbsoluteVolume addr="
+        sVolumeLogger.enqueue(new EventLogger.StringEvent("avrcpSupportsAbsoluteVolume addr="
                 + address + " support=" + support).printLog(TAG));
         mDeviceBroker.setAvrcpAbsoluteVolumeSupported(support);
         setAvrcpAbsoluteVolumeSupported(support);
@@ -8803,13 +8829,21 @@
                     UserInfo userInfo = UserManagerService.getInstance().getUserInfo(userId);
                     killBackgroundUserProcessesWithRecordAudioPermission(userInfo);
                 }
-                UserManagerService.getInstance().setUserRestriction(
-                        UserManager.DISALLOW_RECORD_AUDIO, true, userId);
+                try {
+                    UserManagerService.getInstance().setUserRestriction(
+                            UserManager.DISALLOW_RECORD_AUDIO, true, userId);
+                } catch (IllegalArgumentException e) {
+                    Slog.w(TAG, "Failed to apply DISALLOW_RECORD_AUDIO restriction: " + e);
+                }
             } else if (action.equals(Intent.ACTION_USER_FOREGROUND)) {
                 // Enable audio recording for foreground user/profile
                 int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
-                UserManagerService.getInstance().setUserRestriction(
-                        UserManager.DISALLOW_RECORD_AUDIO, false, userId);
+                try {
+                    UserManagerService.getInstance().setUserRestriction(
+                            UserManager.DISALLOW_RECORD_AUDIO, false, userId);
+                } catch (IllegalArgumentException e) {
+                    Slog.w(TAG, "Failed to apply DISALLOW_RECORD_AUDIO restriction: " + e);
+                }
             } else if (action.equals(BluetoothAdapter.ACTION_STATE_CHANGED)) {
                 state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, -1);
                 if (state == BluetoothAdapter.STATE_OFF ||
@@ -10754,7 +10788,7 @@
                 pcb.asBinder().linkToDeath(app, 0/*flags*/);
 
                 // logging after registration so we have the registration id
-                mDynPolicyLogger.log((new EventLogger.StringEvent("registerAudioPolicy for "
+                mDynPolicyLogger.enqueue((new EventLogger.StringEvent("registerAudioPolicy for "
                         + pcb.asBinder() + " u/pid:" + Binder.getCallingUid() + "/"
                         + Binder.getCallingPid() + " with config:" + app.toCompactLogString()))
                         .printLog(TAG));
@@ -10952,7 +10986,7 @@
 
 
     private void unregisterAudioPolicyInt(@NonNull IAudioPolicyCallback pcb, String operationName) {
-        mDynPolicyLogger.log((new EventLogger.StringEvent(operationName + " for "
+        mDynPolicyLogger.enqueue((new EventLogger.StringEvent(operationName + " for "
                 + pcb.asBinder()).printLog(TAG)));
         synchronized (mAudioPolicies) {
             AudioPolicyProxy app = mAudioPolicies.remove(pcb.asBinder());
@@ -11482,7 +11516,7 @@
         }
 
         public void binderDied() {
-            mDynPolicyLogger.log((new EventLogger.StringEvent("AudioPolicy "
+            mDynPolicyLogger.enqueue((new EventLogger.StringEvent("AudioPolicy "
                     + mPolicyCallback.asBinder() + " died").printLog(TAG)));
             release();
         }
diff --git a/services/core/java/com/android/server/audio/BtHelper.java b/services/core/java/com/android/server/audio/BtHelper.java
index 399829e..df65dbd 100644
--- a/services/core/java/com/android/server/audio/BtHelper.java
+++ b/services/core/java/com/android/server/audio/BtHelper.java
@@ -263,20 +263,20 @@
     /*package*/ synchronized void setAvrcpAbsoluteVolumeIndex(int index) {
         if (mA2dp == null) {
             if (AudioService.DEBUG_VOL) {
-                AudioService.sVolumeLogger.log(new EventLogger.StringEvent(
+                AudioService.sVolumeLogger.enqueue(new EventLogger.StringEvent(
                         "setAvrcpAbsoluteVolumeIndex: bailing due to null mA2dp").printLog(TAG));
                 return;
             }
         }
         if (!mAvrcpAbsVolSupported) {
-            AudioService.sVolumeLogger.log(new EventLogger.StringEvent(
+            AudioService.sVolumeLogger.enqueue(new EventLogger.StringEvent(
                     "setAvrcpAbsoluteVolumeIndex: abs vol not supported ").printLog(TAG));
             return;
         }
         if (AudioService.DEBUG_VOL) {
             Log.i(TAG, "setAvrcpAbsoluteVolumeIndex index=" + index);
         }
-        AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent(
+        AudioService.sVolumeLogger.enqueue(new AudioServiceEvents.VolumeEvent(
                 AudioServiceEvents.VolumeEvent.VOL_SET_AVRCP_VOL, index));
         mA2dp.setAvrcpAbsoluteVolume(index);
     }
@@ -393,14 +393,14 @@
     @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
     /*package*/ synchronized boolean startBluetoothSco(int scoAudioMode,
                 @NonNull String eventSource) {
-        AudioService.sDeviceLogger.log(new EventLogger.StringEvent(eventSource));
+        AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(eventSource));
         return requestScoState(BluetoothHeadset.STATE_AUDIO_CONNECTED, scoAudioMode);
     }
 
     // @GuardedBy("AudioDeviceBroker.mSetModeLock")
     @GuardedBy("AudioDeviceBroker.mDeviceStateLock")
     /*package*/ synchronized boolean stopBluetoothSco(@NonNull String eventSource) {
-        AudioService.sDeviceLogger.log(new EventLogger.StringEvent(eventSource));
+        AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(eventSource));
         return requestScoState(BluetoothHeadset.STATE_AUDIO_DISCONNECTED, SCO_MODE_VIRTUAL_CALL);
     }
 
@@ -418,7 +418,7 @@
             Log.i(TAG, "setLeAudioVolume: calling mLeAudio.setVolume idx="
                     + index + " volume=" + volume);
         }
-        AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent(
+        AudioService.sVolumeLogger.enqueue(new AudioServiceEvents.VolumeEvent(
                 AudioServiceEvents.VolumeEvent.VOL_SET_LE_AUDIO_VOL, index, maxIndex));
         mLeAudio.setVolume(volume);
     }
@@ -443,7 +443,7 @@
         }
         // do not log when hearing aid is not connected to avoid confusion when reading dumpsys
         if (isHeadAidConnected) {
-            AudioService.sVolumeLogger.log(new AudioServiceEvents.VolumeEvent(
+            AudioService.sVolumeLogger.enqueue(new AudioServiceEvents.VolumeEvent(
                     AudioServiceEvents.VolumeEvent.VOL_SET_HEARING_AID_VOL, index, gainDB));
         }
         mHearingAid.setVolume(gainDB);
@@ -675,7 +675,7 @@
                         case BluetoothProfile.HEADSET:
                         case BluetoothProfile.HEARING_AID:
                         case BluetoothProfile.LE_AUDIO:
-                            AudioService.sDeviceLogger.log(new EventLogger.StringEvent(
+                            AudioService.sDeviceLogger.enqueue(new EventLogger.StringEvent(
                                     "BT profile service: connecting "
                                     + BluetoothProfile.getProfileName(profile) + " profile"));
                             mDeviceBroker.postBtProfileConnected(profile, proxy);
diff --git a/services/core/java/com/android/server/audio/FadeOutManager.java b/services/core/java/com/android/server/audio/FadeOutManager.java
index e54ee86..5f6f4b1 100644
--- a/services/core/java/com/android/server/audio/FadeOutManager.java
+++ b/services/core/java/com/android/server/audio/FadeOutManager.java
@@ -245,7 +245,7 @@
                 return;
             }
             try {
-                PlaybackActivityMonitor.sEventLogger.log(
+                PlaybackActivityMonitor.sEventLogger.enqueue(
                         (new PlaybackActivityMonitor.FadeOutEvent(apc, skipRamp)).printLog(TAG));
                 apc.getPlayerProxy().applyVolumeShaper(
                         FADEOUT_VSHAPE,
@@ -262,7 +262,7 @@
                 final AudioPlaybackConfiguration apc = players.get(piid);
                 if (apc != null) {
                     try {
-                        PlaybackActivityMonitor.sEventLogger.log(
+                        PlaybackActivityMonitor.sEventLogger.enqueue(
                                 (new EventLogger.StringEvent("unfading out piid:"
                                         + piid)).printLog(TAG));
                         apc.getPlayerProxy().applyVolumeShaper(
diff --git a/services/core/java/com/android/server/audio/MediaFocusControl.java b/services/core/java/com/android/server/audio/MediaFocusControl.java
index 1ca27dd..27687b2 100644
--- a/services/core/java/com/android/server/audio/MediaFocusControl.java
+++ b/services/core/java/com/android/server/audio/MediaFocusControl.java
@@ -185,7 +185,7 @@
                 final FocusRequester focusOwner = stackIterator.next();
                 if (focusOwner.hasSameUid(uid) && focusOwner.hasSamePackage(packageName)) {
                     clientsToRemove.add(focusOwner.getClientId());
-                    mEventLogger.log((new EventLogger.StringEvent(
+                    mEventLogger.enqueue((new EventLogger.StringEvent(
                             "focus owner:" + focusOwner.getClientId()
                                     + " in uid:" + uid + " pack: " + packageName
                                     + " getting AUDIOFOCUS_LOSS due to app suspension"))
@@ -433,7 +433,7 @@
             FocusRequester fr = stackIterator.next();
             if(fr.hasSameBinder(cb)) {
                 Log.i(TAG, "AudioFocus  removeFocusStackEntryOnDeath(): removing entry for " + cb);
-                mEventLogger.log(new EventLogger.StringEvent(
+                mEventLogger.enqueue(new EventLogger.StringEvent(
                         "focus requester:" + fr.getClientId()
                                 + " in uid:" + fr.getClientUid()
                                 + " pack:" + fr.getPackageName()
@@ -470,7 +470,7 @@
             final FocusRequester fr = owner.getValue();
             if (fr.hasSameBinder(cb)) {
                 ownerIterator.remove();
-                mEventLogger.log(new EventLogger.StringEvent(
+                mEventLogger.enqueue(new EventLogger.StringEvent(
                         "focus requester:" + fr.getClientId()
                                 + " in uid:" + fr.getClientUid()
                                 + " pack:" + fr.getPackageName()
@@ -968,7 +968,7 @@
         // supposed to be alone in bitfield
         final int uid = (flags == AudioManager.AUDIOFOCUS_FLAG_TEST)
                 ? testUid : Binder.getCallingUid();
-        mEventLogger.log((new EventLogger.StringEvent(
+        mEventLogger.enqueue((new EventLogger.StringEvent(
                 "requestAudioFocus() from uid/pid " + uid
                     + "/" + Binder.getCallingPid()
                     + " AA=" + aa.usageToString() + "/" + aa.contentTypeToString()
@@ -1143,7 +1143,7 @@
                 .record();
 
         // AudioAttributes are currently ignored, to be used for zones / a11y
-        mEventLogger.log((new EventLogger.StringEvent(
+        mEventLogger.enqueue((new EventLogger.StringEvent(
                 "abandonAudioFocus() from uid/pid " + Binder.getCallingUid()
                     + "/" + Binder.getCallingPid()
                     + " clientId=" + clientId))
diff --git a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
index 1af8c59..0bc4b20 100644
--- a/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/PlaybackActivityMonitor.java
@@ -17,12 +17,12 @@
 package com.android.server.audio;
 
 import static android.media.AudioPlaybackConfiguration.EXTRA_PLAYER_EVENT_MUTE;
-import static android.media.AudioPlaybackConfiguration.PLAYER_MUTE_CLIENT_VOLUME;
-import static android.media.AudioPlaybackConfiguration.PLAYER_MUTE_MASTER;
-import static android.media.AudioPlaybackConfiguration.PLAYER_MUTE_PLAYBACK_RESTRICTED;
-import static android.media.AudioPlaybackConfiguration.PLAYER_MUTE_STREAM_MUTED;
-import static android.media.AudioPlaybackConfiguration.PLAYER_MUTE_STREAM_VOLUME;
-import static android.media.AudioPlaybackConfiguration.PLAYER_MUTE_VOLUME_SHAPER;
+import static android.media.AudioPlaybackConfiguration.MUTED_BY_APP_OPS;
+import static android.media.AudioPlaybackConfiguration.MUTED_BY_CLIENT_VOLUME;
+import static android.media.AudioPlaybackConfiguration.MUTED_BY_MASTER;
+import static android.media.AudioPlaybackConfiguration.MUTED_BY_STREAM_MUTED;
+import static android.media.AudioPlaybackConfiguration.MUTED_BY_STREAM_VOLUME;
+import static android.media.AudioPlaybackConfiguration.MUTED_BY_VOLUME_SHAPER;
 import static android.media.AudioPlaybackConfiguration.PLAYER_PIID_INVALID;
 import static android.media.AudioPlaybackConfiguration.PLAYER_UPDATE_MUTED;
 
@@ -157,7 +157,7 @@
             if (index >= 0) {
                 if (!disable) {
                     if (DEBUG) { // hidden behind DEBUG, too noisy otherwise
-                        sEventLogger.log(new EventLogger.StringEvent("unbanning uid:" + uid));
+                        sEventLogger.enqueue(new EventLogger.StringEvent("unbanning uid:" + uid));
                     }
                     mBannedUids.remove(index);
                     // nothing else to do, future playback requests from this uid are ok
@@ -168,7 +168,7 @@
                         checkBanPlayer(apc, uid);
                     }
                     if (DEBUG) { // hidden behind DEBUG, too noisy otherwise
-                        sEventLogger.log(new EventLogger.StringEvent("banning uid:" + uid));
+                        sEventLogger.enqueue(new EventLogger.StringEvent("banning uid:" + uid));
                     }
                     mBannedUids.add(new Integer(uid));
                 } // no else to handle, uid already not in list, so enabling again is no-op
@@ -209,7 +209,7 @@
                 updateAllowedCapturePolicy(apc, mAllowedCapturePolicies.get(uid));
             }
         }
-        sEventLogger.log(new NewPlayerEvent(apc));
+        sEventLogger.enqueue(new NewPlayerEvent(apc));
         synchronized(mPlayerLock) {
             mPlayers.put(newPiid, apc);
             maybeMutePlayerAwaitingConnection(apc);
@@ -229,7 +229,7 @@
         synchronized(mPlayerLock) {
             final AudioPlaybackConfiguration apc = mPlayers.get(new Integer(piid));
             if (checkConfigurationCaller(piid, apc, binderUid)) {
-                sEventLogger.log(new AudioAttrEvent(piid, attr));
+                sEventLogger.enqueue(new AudioAttrEvent(piid, attr));
                 change = apc.handleAudioAttributesEvent(attr);
             } else {
                 Log.e(TAG, "Error updating audio attributes");
@@ -322,7 +322,7 @@
                 return;
             }
 
-            sEventLogger.log(new PlayerEvent(piid, event, eventValue));
+            sEventLogger.enqueue(new PlayerEvent(piid, event, eventValue));
 
             if (event == AudioPlaybackConfiguration.PLAYER_UPDATE_PORT_ID) {
                 mEventHandler.sendMessage(
@@ -332,7 +332,7 @@
                 for (Integer uidInteger: mBannedUids) {
                     if (checkBanPlayer(apc, uidInteger.intValue())) {
                         // player was banned, do not update its state
-                        sEventLogger.log(new EventLogger.StringEvent(
+                        sEventLogger.enqueue(new EventLogger.StringEvent(
                                 "not starting piid:" + piid + " ,is banned"));
                         return;
                     }
@@ -412,7 +412,7 @@
 
     public void playerHasOpPlayAudio(int piid, boolean hasOpPlayAudio, int binderUid) {
         // no check on UID yet because this is only for logging at the moment
-        sEventLogger.log(new PlayerOpPlayAudioEvent(piid, hasOpPlayAudio, binderUid));
+        sEventLogger.enqueue(new PlayerOpPlayAudioEvent(piid, hasOpPlayAudio, binderUid));
     }
 
     public void releasePlayer(int piid, int binderUid) {
@@ -421,7 +421,7 @@
         synchronized(mPlayerLock) {
             final AudioPlaybackConfiguration apc = mPlayers.get(new Integer(piid));
             if (checkConfigurationCaller(piid, apc, binderUid)) {
-                sEventLogger.log(new EventLogger.StringEvent(
+                sEventLogger.enqueue(new EventLogger.StringEvent(
                         "releasing player piid:" + piid));
                 mPlayers.remove(new Integer(piid));
                 mDuckingManager.removeReleased(apc);
@@ -443,7 +443,7 @@
     }
 
     /*package*/ void onAudioServerDied() {
-        sEventLogger.log(
+        sEventLogger.enqueue(
                 new EventLogger.StringEvent(
                         "clear port id to piid map"));
         synchronized (mPlayerLock) {
@@ -768,7 +768,7 @@
                 }
                 if (mute) {
                     try {
-                        sEventLogger.log((new EventLogger.StringEvent("call: muting piid:"
+                        sEventLogger.enqueue((new EventLogger.StringEvent("call: muting piid:"
                                 + piid + " uid:" + apc.getClientUid())).printLog(TAG));
                         apc.getPlayerProxy().setVolume(0.0f);
                         mMutedPlayers.add(new Integer(piid));
@@ -793,7 +793,7 @@
                 final AudioPlaybackConfiguration apc = mPlayers.get(piid);
                 if (apc != null) {
                     try {
-                        sEventLogger.log(new EventLogger.StringEvent("call: unmuting piid:"
+                        sEventLogger.enqueue(new EventLogger.StringEvent("call: unmuting piid:"
                                 + piid).printLog(TAG));
                         apc.getPlayerProxy().setVolume(1.0f);
                     } catch (Exception e) {
@@ -1081,7 +1081,7 @@
                     return;
                 }
                 try {
-                    sEventLogger.log((new DuckEvent(apc, skipRamp)).printLog(TAG));
+                    sEventLogger.enqueue((new DuckEvent(apc, skipRamp)).printLog(TAG));
                     apc.getPlayerProxy().applyVolumeShaper(
                             DUCK_VSHAPE,
                             skipRamp ? PLAY_SKIP_RAMP : PLAY_CREATE_IF_NEEDED);
@@ -1096,7 +1096,7 @@
                     final AudioPlaybackConfiguration apc = players.get(piid);
                     if (apc != null) {
                         try {
-                            sEventLogger.log((new EventLogger.StringEvent("unducking piid:"
+                            sEventLogger.enqueue((new EventLogger.StringEvent("unducking piid:"
                                     + piid)).printLog(TAG));
                             apc.getPlayerProxy().applyVolumeShaper(
                                     DUCK_ID,
@@ -1155,22 +1155,22 @@
                     if (mEventValue <= 0) {
                         builder.append("none ");
                     } else {
-                        if ((mEventValue & PLAYER_MUTE_MASTER) != 0) {
+                        if ((mEventValue & MUTED_BY_MASTER) != 0) {
                             builder.append("masterMute ");
                         }
-                        if ((mEventValue & PLAYER_MUTE_STREAM_VOLUME) != 0) {
+                        if ((mEventValue & MUTED_BY_STREAM_VOLUME) != 0) {
                             builder.append("streamVolume ");
                         }
-                        if ((mEventValue & PLAYER_MUTE_STREAM_MUTED) != 0) {
+                        if ((mEventValue & MUTED_BY_STREAM_MUTED) != 0) {
                             builder.append("streamMute ");
                         }
-                        if ((mEventValue & PLAYER_MUTE_PLAYBACK_RESTRICTED) != 0) {
-                            builder.append("playbackRestricted ");
+                        if ((mEventValue & MUTED_BY_APP_OPS) != 0) {
+                            builder.append("appOps ");
                         }
-                        if ((mEventValue & PLAYER_MUTE_CLIENT_VOLUME) != 0) {
+                        if ((mEventValue & MUTED_BY_CLIENT_VOLUME) != 0) {
                             builder.append("clientVolume ");
                         }
-                        if ((mEventValue & PLAYER_MUTE_VOLUME_SHAPER) != 0) {
+                        if ((mEventValue & MUTED_BY_VOLUME_SHAPER) != 0) {
                             builder.append("volumeShaper ");
                         }
                     }
@@ -1310,8 +1310,9 @@
     //==========================================================================================
     void muteAwaitConnection(@NonNull int[] usagesToMute,
             @NonNull AudioDeviceAttributes dev, long timeOutMs) {
-        sEventLogger.loglogi(
-                "muteAwaitConnection() dev:" + dev + " timeOutMs:" + timeOutMs, TAG);
+        sEventLogger.enqueueAndLog(
+                "muteAwaitConnection() dev:" + dev + " timeOutMs:" + timeOutMs,
+                EventLogger.Event.ALOGI, TAG);
         synchronized (mPlayerLock) {
             mutePlayersExpectingDevice(usagesToMute);
             // schedule timeout (remove previously scheduled first)
@@ -1323,7 +1324,8 @@
     }
 
     void cancelMuteAwaitConnection(String source) {
-        sEventLogger.loglogi("cancelMuteAwaitConnection() from:" + source, TAG);
+        sEventLogger.enqueueAndLog("cancelMuteAwaitConnection() from:" + source,
+                EventLogger.Event.ALOGI, TAG);
         synchronized (mPlayerLock) {
             // cancel scheduled timeout, ignore device, only one expected device at a time
             mEventHandler.removeMessages(MSG_L_TIMEOUT_MUTE_AWAIT_CONNECTION);
@@ -1346,7 +1348,7 @@
 
     @GuardedBy("mPlayerLock")
     private void mutePlayersExpectingDevice(@NonNull int[] usagesToMute) {
-        sEventLogger.log(new MuteAwaitConnectionEvent(usagesToMute));
+        sEventLogger.enqueue(new MuteAwaitConnectionEvent(usagesToMute));
         mMutedUsagesAwaitingConnection = usagesToMute;
         final Set<Integer> piidSet = mPlayers.keySet();
         final Iterator<Integer> piidIterator = piidSet.iterator();
@@ -1369,7 +1371,7 @@
         for (int usage : mMutedUsagesAwaitingConnection) {
             if (usage == apc.getAudioAttributes().getUsage()) {
                 try {
-                    sEventLogger.log((new EventLogger.StringEvent(
+                    sEventLogger.enqueue((new EventLogger.StringEvent(
                             "awaiting connection: muting piid:"
                                     + apc.getPlayerInterfaceId()
                                     + " uid:" + apc.getClientUid())).printLog(TAG));
@@ -1394,7 +1396,7 @@
                 continue;
             }
             try {
-                sEventLogger.log(new EventLogger.StringEvent(
+                sEventLogger.enqueue(new EventLogger.StringEvent(
                         "unmuting piid:" + piid).printLog(TAG));
                 apc.getPlayerProxy().applyVolumeShaper(MUTE_AWAIT_CONNECTION_VSHAPE,
                         VolumeShaper.Operation.REVERSE);
@@ -1452,8 +1454,9 @@
             public void handleMessage(Message msg) {
                 switch (msg.what) {
                     case MSG_L_TIMEOUT_MUTE_AWAIT_CONNECTION:
-                        sEventLogger.loglogi("Timeout for muting waiting for "
-                                + (AudioDeviceAttributes) msg.obj + ", unmuting", TAG);
+                        sEventLogger.enqueueAndLog("Timeout for muting waiting for "
+                                + (AudioDeviceAttributes) msg.obj + ", unmuting",
+                                EventLogger.Event.ALOGI, TAG);
                         synchronized (mPlayerLock) {
                             unmutePlayersExpectingDevice();
                         }
@@ -1476,7 +1479,7 @@
                         synchronized (mPlayerLock) {
                             int piid = msg.arg1;
 
-                            sEventLogger.log(
+                            sEventLogger.enqueue(
                                     new PlayerEvent(piid, PLAYER_UPDATE_MUTED, eventValue));
 
                             final AudioPlaybackConfiguration apc = mPlayers.get(piid);
diff --git a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
index 2ba8882..652ea52 100644
--- a/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
+++ b/services/core/java/com/android/server/audio/RecordingActivityMonitor.java
@@ -164,7 +164,7 @@
         }
         if (MediaRecorder.isSystemOnlyAudioSource(source)) {
             // still want to log event, it just won't appear in recording configurations;
-            sEventLogger.log(new RecordingEvent(event, riid, config).printLog(TAG));
+            sEventLogger.enqueue(new RecordingEvent(event, riid, config).printLog(TAG));
             return;
         }
         dispatchCallbacks(updateSnapshot(event, riid, config));
@@ -204,7 +204,7 @@
                 ? AudioManager.RECORD_CONFIG_EVENT_STOP : AudioManager.RECORD_CONFIG_EVENT_NONE;
         if (riid == AudioManager.RECORD_RIID_INVALID
                 || configEvent == AudioManager.RECORD_CONFIG_EVENT_NONE) {
-            sEventLogger.log(new RecordingEvent(event, riid, null).printLog(TAG));
+            sEventLogger.enqueue(new RecordingEvent(event, riid, null).printLog(TAG));
             return;
         }
         dispatchCallbacks(updateSnapshot(configEvent, riid, null));
@@ -301,7 +301,7 @@
                 if (!state.hasDeathHandler()) {
                     if (state.isActiveConfiguration()) {
                         configChanged = true;
-                        sEventLogger.log(new RecordingEvent(
+                        sEventLogger.enqueue(new RecordingEvent(
                                         AudioManager.RECORD_CONFIG_EVENT_RELEASE,
                                         state.getRiid(), state.getConfig()));
                     }
@@ -486,7 +486,7 @@
                     configChanged = false;
             }
             if (configChanged) {
-                sEventLogger.log(new RecordingEvent(event, riid, state.getConfig()));
+                sEventLogger.enqueue(new RecordingEvent(event, riid, state.getConfig()));
                 configs = getActiveRecordingConfigurations(true /*isPrivileged*/);
             }
         }
diff --git a/services/core/java/com/android/server/audio/SoundEffectsHelper.java b/services/core/java/com/android/server/audio/SoundEffectsHelper.java
index 93eba50..79b54eb 100644
--- a/services/core/java/com/android/server/audio/SoundEffectsHelper.java
+++ b/services/core/java/com/android/server/audio/SoundEffectsHelper.java
@@ -164,7 +164,7 @@
     }
 
     private void logEvent(String msg) {
-        mSfxLogger.log(new EventLogger.StringEvent(msg));
+        mSfxLogger.enqueue(new EventLogger.StringEvent(msg));
     }
 
     // All the methods below run on the worker thread
diff --git a/services/core/java/com/android/server/audio/SpatializerHelper.java b/services/core/java/com/android/server/audio/SpatializerHelper.java
index 1563d33..2b525f1 100644
--- a/services/core/java/com/android/server/audio/SpatializerHelper.java
+++ b/services/core/java/com/android/server/audio/SpatializerHelper.java
@@ -1708,11 +1708,11 @@
 
 
     private static void loglogi(String msg) {
-        AudioService.sSpatialLogger.loglogi(msg, TAG);
+        AudioService.sSpatialLogger.enqueueAndLog(msg, EventLogger.Event.ALOGI, TAG);
     }
 
     private static String logloge(String msg) {
-        AudioService.sSpatialLogger.loglog(msg, EventLogger.Event.ALOGE, TAG);
+        AudioService.sSpatialLogger.enqueueAndLog(msg, EventLogger.Event.ALOGE, TAG);
         return msg;
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
index 1d90954..055c63d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AcquisitionClient.java
@@ -38,8 +38,7 @@
  * Abstract {@link HalClientMonitor} subclass that operations eligible/interested in acquisition
  * messages should extend.
  */
-public abstract class AcquisitionClient<T> extends HalClientMonitor<T> implements Interruptable,
-        ErrorConsumer {
+public abstract class AcquisitionClient<T> extends HalClientMonitor<T> implements ErrorConsumer {
 
     private static final String TAG = "Biometrics/AcquisitionClient";
 
@@ -217,4 +216,9 @@
                     HARDWARE_FEEDBACK_VIBRATION_ATTRIBUTES);
         }
     }
+
+    @Override
+    public boolean isInterruptable() {
+        return true;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
index da7781a..0216e49 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BaseClientMonitor.java
@@ -193,9 +193,9 @@
         }
 
         // If the current client dies we should cancel the current operation.
-        if (this instanceof Interruptable) {
+        if (this.isInterruptable()) {
             Slog.e(TAG, "Binder died, cancelling client");
-            ((Interruptable) this).cancel();
+            this.cancel();
         }
         mToken = null;
         if (clearListener) {
@@ -320,4 +320,12 @@
         }
         callback.onClientFinished(this, true /* success */);
     }
+
+    /**
+     * Checks if other client monitor can interrupt current client monitor
+     * @return if current client can be interrupted
+     */
+    public boolean isInterruptable() {
+        return false;
+    }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java b/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java
index dacec38..4825f1d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java
+++ b/services/core/java/com/android/server/biometrics/sensors/BiometricSchedulerOperation.java
@@ -46,7 +46,7 @@
 
     /**
      * The operation is added to the list of pending operations, but a subsequent operation
-     * has been added. This state only applies to {@link Interruptable} operations. When this
+     * has been added. This state only applies to interruptable operations. When this
      * operation reaches the head of the queue, it will send ERROR_CANCELED and finish.
      */
     protected static final int STATE_WAITING_IN_QUEUE_CANCELING = 1;
@@ -347,9 +347,9 @@
         return mClientMonitor == clientMonitor;
     }
 
-    /** If this operation is {@link Interruptable}. */
+    /** If this operation is interruptable. */
     public boolean isInterruptable() {
-        return mClientMonitor instanceof Interruptable;
+        return mClientMonitor.isInterruptable();
     }
 
     private boolean isHalOperation() {
diff --git a/services/core/java/com/android/server/biometrics/sensors/Interruptable.java b/services/core/java/com/android/server/biometrics/sensors/Interruptable.java
deleted file mode 100644
index 4f645ef..0000000
--- a/services/core/java/com/android/server/biometrics/sensors/Interruptable.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.biometrics.sensors;
-
-import android.annotation.NonNull;
-
-/**
- * Interface that {@link BaseClientMonitor} subclasses eligible for cancellation should implement.
- */
-public interface Interruptable {
-    /**
-     * Requests to end the ClientMonitor's lifecycle.
-     */
-    void cancel();
-
-    /**
-     * Notifies the client that it needs to finish before
-     * {@link BaseClientMonitor#start(ClientMonitorCallback)} was invoked. This usually happens
-     * if the client is still waiting in the pending queue and got notified that a subsequent
-     * operation is preempting it.
-     *
-     * This method must invoke
-     * {@link ClientMonitorCallback#onClientFinished(BaseClientMonitor, boolean)} on the
-     * given callback (with success).
-     *
-     * @param callback invoked when the operation is completed.
-     */
-    void cancelWithoutStarting(@NonNull ClientMonitorCallback callback);
-}
diff --git a/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java b/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java
index aeb6b6e..969a174 100644
--- a/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java
+++ b/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.hardware.biometrics.BiometricOverlayConstants;
 import android.hardware.fingerprint.ISidefpsController;
+import android.hardware.fingerprint.IUdfpsOverlay;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.hardware.fingerprint.IUdfpsOverlayControllerCallback;
 import android.os.RemoteException;
@@ -43,6 +44,7 @@
 
     @NonNull private final Optional<IUdfpsOverlayController> mUdfpsOverlayController;
     @NonNull private final Optional<ISidefpsController> mSidefpsController;
+    @NonNull private final Optional<IUdfpsOverlay> mUdfpsOverlay;
 
     /**
      * Create an overlay controller for each modality.
@@ -52,9 +54,11 @@
      */
     public SensorOverlays(
             @Nullable IUdfpsOverlayController udfpsOverlayController,
-            @Nullable ISidefpsController sidefpsController) {
+            @Nullable ISidefpsController sidefpsController,
+            @Nullable IUdfpsOverlay udfpsOverlay) {
         mUdfpsOverlayController = Optional.ofNullable(udfpsOverlayController);
         mSidefpsController = Optional.ofNullable(sidefpsController);
+        mUdfpsOverlay = Optional.ofNullable(udfpsOverlay);
     }
 
     /**
@@ -90,6 +94,14 @@
                 Slog.e(TAG, "Remote exception when showing the UDFPS overlay", e);
             }
         }
+
+        if (mUdfpsOverlay.isPresent()) {
+            try {
+                mUdfpsOverlay.get().show(client.getRequestId(), sensorId, reason);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Remote exception when showing the new UDFPS overlay", e);
+            }
+        }
     }
 
     /**
@@ -113,6 +125,14 @@
                 Slog.e(TAG, "Remote exception when hiding the UDFPS overlay", e);
             }
         }
+
+        if (mUdfpsOverlay.isPresent()) {
+            try {
+                mUdfpsOverlay.get().hide(sensorId);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Remote exception when hiding the new udfps overlay", e);
+            }
+        }
     }
 
     /**
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
index 800d4b8..0f5cdc3 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/aidl/Sensor.java
@@ -59,7 +59,6 @@
 import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.EnumerateConsumer;
 import com.android.server.biometrics.sensors.ErrorConsumer;
-import com.android.server.biometrics.sensors.Interruptable;
 import com.android.server.biometrics.sensors.LockoutCache;
 import com.android.server.biometrics.sensors.LockoutConsumer;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
@@ -638,7 +637,7 @@
 
     public void onBinderDied() {
         final BaseClientMonitor client = mScheduler.getCurrentClient();
-        if (client instanceof Interruptable) {
+        if (client.isInterruptable()) {
             Slog.e(mTag, "Sending ERROR_HW_UNAVAILABLE for client: " + client);
             final ErrorConsumer errorConsumer = (ErrorConsumer) client;
             errorConsumer.onError(FaceManager.FACE_ERROR_HW_UNAVAILABLE,
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 4d3173e..70470e9 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -52,6 +52,7 @@
 import android.hardware.fingerprint.IFingerprintService;
 import android.hardware.fingerprint.IFingerprintServiceReceiver;
 import android.hardware.fingerprint.ISidefpsController;
+import android.hardware.fingerprint.IUdfpsOverlay;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.Binder;
 import android.os.Build;
@@ -940,6 +941,14 @@
 
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
+        public void setUdfpsOverlay(@NonNull IUdfpsOverlay controller) {
+            for (ServiceProvider provider : mRegistry.getProviders()) {
+                provider.setUdfpsOverlay(controller);
+            }
+        }
+
+        @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
+        @Override
         public void onPowerPressed() {
             super.onPowerPressed_enforcePermission();
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
index 0c29f56..05c2e29 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
@@ -26,6 +26,7 @@
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.IFingerprintServiceReceiver;
 import android.hardware.fingerprint.ISidefpsController;
+import android.hardware.fingerprint.IUdfpsOverlay;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.IBinder;
 
@@ -129,6 +130,12 @@
 
     void setUdfpsOverlayController(@NonNull IUdfpsOverlayController controller);
 
+    /**
+     * Sets udfps overlay
+     * @param controller udfps overlay
+     */
+    void setUdfpsOverlay(@NonNull IUdfpsOverlay controller);
+
     void onPowerPressed();
 
     /**
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index 2e5663d..49bd44b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -33,6 +33,7 @@
 import android.hardware.biometrics.fingerprint.PointerContext;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.ISidefpsController;
+import android.hardware.fingerprint.IUdfpsOverlay;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.Build;
 import android.os.Handler;
@@ -71,7 +72,6 @@
 class FingerprintAuthenticationClient extends AuthenticationClient<AidlSession>
         implements Udfps, LockoutConsumer, PowerPressHandler {
     private static final String TAG = "FingerprintAuthenticationClient";
-    private static final int MESSAGE_IGNORE_AUTH = 1;
     private static final int MESSAGE_AUTH_SUCCESS = 2;
     private static final int MESSAGE_FINGER_UP = 3;
     @NonNull
@@ -118,6 +118,7 @@
             @NonNull LockoutCache lockoutCache,
             @Nullable IUdfpsOverlayController udfpsOverlayController,
             @Nullable ISidefpsController sidefpsController,
+            @Nullable IUdfpsOverlay udfpsOverlay,
             boolean allowBackgroundAuthentication,
             @NonNull FingerprintSensorPropertiesInternal sensorProps,
             @NonNull Handler handler,
@@ -145,7 +146,8 @@
                 false /* isKeyguardBypassEnabled */);
         setRequestId(requestId);
         mLockoutCache = lockoutCache;
-        mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
+        mSensorOverlays = new SensorOverlays(udfpsOverlayController,
+                sidefpsController, udfpsOverlay);
         mSensorProps = sensorProps;
         mALSProbeCallback = getLogger().getAmbientLightProbe(false /* startWithClient */);
         mHandler = handler;
@@ -246,12 +248,6 @@
                 () -> {
                     long delay = 0;
                     if (authenticated && mSensorProps.isAnySidefpsType()) {
-                        if (mHandler.hasMessages(MESSAGE_IGNORE_AUTH)) {
-                            Slog.i(TAG, "(sideFPS) Ignoring auth due to recent power press");
-                            onErrorInternal(BiometricConstants.BIOMETRIC_ERROR_POWER_PRESSED, 0,
-                                    true);
-                            return;
-                        }
                         delay = isKeyguard() ? mWaitForAuthKeyguard : mWaitForAuthBp;
 
                         if (mSideFpsLastAcquireStartTime != -1) {
@@ -512,18 +508,15 @@
         if (mSensorProps.isAnySidefpsType()) {
             Slog.i(TAG, "(sideFPS): onPowerPressed");
             mHandler.post(() -> {
-                if (mHandler.hasMessages(MESSAGE_AUTH_SUCCESS)) {
-                    Slog.i(TAG, "(sideFPS): Ignoring auth in queue");
-                    mHandler.removeMessages(MESSAGE_AUTH_SUCCESS);
-                    // Do not call onError() as that will send an additional callback to coex.
-                    onErrorInternal(BiometricConstants.BIOMETRIC_ERROR_POWER_PRESSED, 0, true);
-                    mAuthSessionCoordinator.authEndedFor(getTargetUserId(),
-                            mBiometricStrength, getSensorId(), getRequestId());
-                }
-                mHandler.removeMessages(MESSAGE_IGNORE_AUTH);
-                mHandler.postDelayed(() -> {
-                }, MESSAGE_IGNORE_AUTH, mIgnoreAuthFor);
-
+                Slog.i(TAG, "(sideFPS): finishing auth");
+                // Ignore auths after a power has been detected
+                mHandler.removeMessages(MESSAGE_AUTH_SUCCESS);
+                // Do not call onError() as that will send an additional callback to coex.
+                onErrorInternal(BiometricConstants.BIOMETRIC_ERROR_POWER_PRESSED,
+                        0, true);
+                mSensorOverlays.hide(getSensorId());
+                mAuthSessionCoordinator.authEndedFor(getTargetUserId(),
+                        mBiometricStrength, getSensorId(), getRequestId());
             });
         }
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
index 0e89814..5282234 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
@@ -21,6 +21,7 @@
 import android.content.Context;
 import android.hardware.biometrics.BiometricOverlayConstants;
 import android.hardware.biometrics.common.ICancellationSignal;
+import android.hardware.fingerprint.IUdfpsOverlay;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -54,12 +55,15 @@
             @NonNull ClientMonitorCallbackConverter listener, int userId,
             @NonNull String owner, int sensorId,
             @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
-            @Nullable IUdfpsOverlayController udfpsOverlayController, boolean isStrongBiometric) {
+            @Nullable IUdfpsOverlayController udfpsOverlayController,
+            @Nullable IUdfpsOverlay udfpsOverlay,
+            boolean isStrongBiometric) {
         super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
                 true /* shouldVibrate */, biometricLogger, biometricContext);
         setRequestId(requestId);
         mIsStrongBiometric = isStrongBiometric;
-        mSensorOverlays = new SensorOverlays(udfpsOverlayController, null /* sideFpsController*/);
+        mSensorOverlays = new SensorOverlays(udfpsOverlayController,
+                null /* sideFpsController*/, udfpsOverlay);
     }
 
     @Override
@@ -82,7 +86,8 @@
 
     @Override
     protected void startHalOperation() {
-        mSensorOverlays.show(getSensorId(), BiometricOverlayConstants.REASON_AUTH_KEYGUARD, this);
+        mSensorOverlays.show(getSensorId(), BiometricOverlayConstants.REASON_AUTH_KEYGUARD,
+                this);
 
         try {
             mCancellationSignal = doDetectInteraction();
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
index 612d906..7e5d39f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
@@ -30,6 +30,7 @@
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.ISidefpsController;
+import android.hardware.fingerprint.IUdfpsOverlay;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.hardware.keymaster.HardwareAuthToken;
 import android.os.IBinder;
@@ -86,6 +87,7 @@
             @NonNull FingerprintSensorPropertiesInternal sensorProps,
             @Nullable IUdfpsOverlayController udfpsOverlayController,
             @Nullable ISidefpsController sidefpsController,
+            @Nullable IUdfpsOverlay udfpsOverlay,
             int maxTemplatesPerUser, @FingerprintManager.EnrollReason int enrollReason) {
         // UDFPS haptics occur when an image is acquired (instead of when the result is known)
         super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
@@ -93,7 +95,8 @@
                 biometricContext);
         setRequestId(requestId);
         mSensorProps = sensorProps;
-        mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
+        mSensorOverlays = new SensorOverlays(udfpsOverlayController,
+                sidefpsController, udfpsOverlay);
         mMaxTemplatesPerUser = maxTemplatesPerUser;
 
         mALSProbeCallback = getLogger().getAmbientLightProbe(true /* startWithClient */);
@@ -162,7 +165,8 @@
 
     @Override
     protected void startHalOperation() {
-        mSensorOverlays.show(getSensorId(), getOverlayReasonFromEnrollReason(mEnrollReason), this);
+        mSensorOverlays.show(getSensorId(), getOverlayReasonFromEnrollReason(mEnrollReason),
+                this);
 
         BiometricNotificationUtils.cancelBadCalibrationNotification(getContext());
         try {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 628c16a..a42ff9a 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -40,6 +40,7 @@
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.IFingerprintServiceReceiver;
 import android.hardware.fingerprint.ISidefpsController;
+import android.hardware.fingerprint.IUdfpsOverlay;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.Binder;
 import android.os.Handler;
@@ -108,6 +109,7 @@
     @Nullable private IFingerprint mDaemon;
     @Nullable private IUdfpsOverlayController mUdfpsOverlayController;
     @Nullable private ISidefpsController mSidefpsController;
+    @Nullable private IUdfpsOverlay mUdfpsOverlay;
 
     private final class BiometricTaskStackListener extends TaskStackListener {
         @Override
@@ -383,7 +385,8 @@
                             BiometricsProtoEnums.CLIENT_UNKNOWN),
                     mBiometricContext,
                     mSensors.get(sensorId).getSensorProperties(),
-                    mUdfpsOverlayController, mSidefpsController, maxTemplatesPerUser, enrollReason);
+                    mUdfpsOverlayController, mSidefpsController, mUdfpsOverlay,
+                    maxTemplatesPerUser, enrollReason);
             scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback(
                     mBiometricStateCallback, new ClientMonitorCallback() {
                 @Override
@@ -418,7 +421,7 @@
                     opPackageName, sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
                     mBiometricContext,
-                    mUdfpsOverlayController, isStrongBiometric);
+                    mUdfpsOverlayController, mUdfpsOverlay, isStrongBiometric);
             scheduleForSensor(sensorId, client, mBiometricStateCallback);
         });
 
@@ -439,10 +442,10 @@
                     createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
                     mBiometricContext, isStrongBiometric,
                     mTaskStackListener, mSensors.get(sensorId).getLockoutCache(),
-                    mUdfpsOverlayController, mSidefpsController, allowBackgroundAuthentication,
+                    mUdfpsOverlayController, mSidefpsController, mUdfpsOverlay,
+                    allowBackgroundAuthentication,
                     mSensors.get(sensorId).getSensorProperties(), mHandler,
-                    Utils.getCurrentStrength(sensorId),
-                    SystemClock.elapsedRealtimeClock());
+                    Utils.getCurrentStrength(sensorId), SystemClock.elapsedRealtimeClock());
             scheduleForSensor(sensorId, client, mBiometricStateCallback);
         });
     }
@@ -649,6 +652,11 @@
     }
 
     @Override
+    public void setUdfpsOverlay(@NonNull IUdfpsOverlay controller) {
+        mUdfpsOverlay = controller;
+    }
+
+    @Override
     public void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto,
             boolean clearSchedulerBuffer) {
         if (mSensors.contains(sensorId)) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index 0e6df8e..dbc96df 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -38,6 +38,7 @@
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.IFingerprintServiceReceiver;
 import android.hardware.fingerprint.ISidefpsController;
+import android.hardware.fingerprint.IUdfpsOverlay;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.Handler;
 import android.os.IBinder;
@@ -120,6 +121,7 @@
     @NonNull private final HalResultController mHalResultController;
     @Nullable private IUdfpsOverlayController mUdfpsOverlayController;
     @Nullable private ISidefpsController mSidefpsController;
+    @Nullable private IUdfpsOverlay mUdfpsOverlay;
     @NonNull private final BiometricContext mBiometricContext;
     // for requests that do not use biometric prompt
     @NonNull private final AtomicLong mRequestCounter = new AtomicLong(0);
@@ -594,7 +596,7 @@
                     createLogger(BiometricsProtoEnums.ACTION_ENROLL,
                             BiometricsProtoEnums.CLIENT_UNKNOWN),
                     mBiometricContext,
-                    mUdfpsOverlayController, mSidefpsController,
+                    mUdfpsOverlayController, mSidefpsController, mUdfpsOverlay,
                     enrollReason);
             mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
                 @Override
@@ -640,7 +642,7 @@
                     mLazyDaemon, token, id, listener, userId, opPackageName,
                     mSensorProperties.sensorId,
                     createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
-                    mBiometricContext, mUdfpsOverlayController,
+                    mBiometricContext, mUdfpsOverlayController, mUdfpsOverlay,
                     isStrongBiometric);
             mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
         });
@@ -664,7 +666,7 @@
                     createLogger(BiometricsProtoEnums.ACTION_AUTHENTICATE, statsClient),
                     mBiometricContext, isStrongBiometric,
                     mTaskStackListener, mLockoutTracker,
-                    mUdfpsOverlayController, mSidefpsController,
+                    mUdfpsOverlayController, mSidefpsController, mUdfpsOverlay,
                     allowBackgroundAuthentication, mSensorProperties);
             mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
         });
@@ -853,6 +855,11 @@
     }
 
     @Override
+    public void setUdfpsOverlay(@NonNull IUdfpsOverlay controller) {
+        mUdfpsOverlay = controller;
+    }
+
+    @Override
     public void dumpProtoState(int sensorId, @NonNull ProtoOutputStream proto,
             boolean clearSchedulerBuffer) {
         final long sensorToken = proto.start(SensorServiceStateProto.SENSOR_STATES);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
index 0d620fd..56fa36e 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
@@ -26,6 +26,7 @@
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.ISidefpsController;
+import android.hardware.fingerprint.IUdfpsOverlay;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -76,15 +77,18 @@
             @NonNull LockoutFrameworkImpl lockoutTracker,
             @Nullable IUdfpsOverlayController udfpsOverlayController,
             @Nullable ISidefpsController sidefpsController,
+            @Nullable IUdfpsOverlay udfpsOverlay,
             boolean allowBackgroundAuthentication,
             @NonNull FingerprintSensorPropertiesInternal sensorProps) {
         super(context, lazyDaemon, token, listener, targetUserId, operationId, restricted,
                 owner, cookie, requireConfirmation, sensorId, logger, biometricContext,
-                isStrongBiometric, taskStackListener, lockoutTracker, allowBackgroundAuthentication,
-                false /* shouldVibrate */, false /* isKeyguardBypassEnabled */);
+                isStrongBiometric, taskStackListener, lockoutTracker,
+                allowBackgroundAuthentication, false /* shouldVibrate */,
+                false /* isKeyguardBypassEnabled */);
         setRequestId(requestId);
         mLockoutFrameworkImpl = lockoutTracker;
-        mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
+        mSensorOverlays = new SensorOverlays(udfpsOverlayController,
+                sidefpsController, udfpsOverlay);
         mSensorProps = sensorProps;
         mALSProbeCallback = getLogger().getAmbientLightProbe(false /* startWithClient */);
     }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
index c2929d0..3e9b8ef 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
@@ -23,6 +23,7 @@
 import android.hardware.biometrics.BiometricFingerprintConstants;
 import android.hardware.biometrics.BiometricOverlayConstants;
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
+import android.hardware.fingerprint.IUdfpsOverlay;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -62,11 +63,13 @@
             @NonNull ClientMonitorCallbackConverter listener, int userId, @NonNull String owner,
             int sensorId,
             @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
-            @Nullable IUdfpsOverlayController udfpsOverlayController, boolean isStrongBiometric) {
+            @Nullable IUdfpsOverlayController udfpsOverlayController,
+            @Nullable IUdfpsOverlay udfpsOverlay, boolean isStrongBiometric) {
         super(context, lazyDaemon, token, listener, userId, owner, 0 /* cookie */, sensorId,
                 true /* shouldVibrate */, biometricLogger, biometricContext);
         setRequestId(requestId);
-        mSensorOverlays = new SensorOverlays(udfpsOverlayController, null /* sideFpsController */);
+        mSensorOverlays = new SensorOverlays(udfpsOverlayController,
+                null /* sideFpsController */, udfpsOverlay);
         mIsStrongBiometric = isStrongBiometric;
     }
 
@@ -92,7 +95,8 @@
 
     @Override
     protected void startHalOperation() {
-        mSensorOverlays.show(getSensorId(), BiometricOverlayConstants.REASON_AUTH_KEYGUARD, this);
+        mSensorOverlays.show(getSensorId(), BiometricOverlayConstants.REASON_AUTH_KEYGUARD,
+                this);
 
         try {
             getFreshDaemon().authenticate(0 /* operationId */, getTargetUserId());
@@ -128,8 +132,8 @@
     }
 
     @Override
-    public void onAuthenticated(BiometricAuthenticator.Identifier identifier, boolean authenticated,
-            ArrayList<Byte> hardwareAuthToken) {
+    public void onAuthenticated(BiometricAuthenticator.Identifier identifier,
+            boolean authenticated, ArrayList<Byte> hardwareAuthToken) {
         getLogger().logOnAuthenticated(getContext(), getOperationContext(),
                 authenticated, false /* requireConfirmation */,
                 getTargetUserId(), false /* isBiometricPrompt */);
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
index 5d9af53..3371cec 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
@@ -26,6 +26,7 @@
 import android.hardware.fingerprint.Fingerprint;
 import android.hardware.fingerprint.FingerprintManager;
 import android.hardware.fingerprint.ISidefpsController;
+import android.hardware.fingerprint.IUdfpsOverlay;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -67,12 +68,14 @@
             @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
             @Nullable IUdfpsOverlayController udfpsOverlayController,
             @Nullable ISidefpsController sidefpsController,
+            @Nullable IUdfpsOverlay udfpsOverlay,
             @FingerprintManager.EnrollReason int enrollReason) {
         super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
                 timeoutSec, sensorId, true /* shouldVibrate */, biometricLogger,
                 biometricContext);
         setRequestId(requestId);
-        mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
+        mSensorOverlays = new SensorOverlays(udfpsOverlayController,
+                sidefpsController, udfpsOverlay);
 
         mEnrollReason = enrollReason;
         if (enrollReason == FingerprintManager.ENROLL_FIND_SENSOR) {
@@ -102,7 +105,8 @@
 
     @Override
     protected void startHalOperation() {
-        mSensorOverlays.show(getSensorId(), getOverlayReasonFromEnrollReason(mEnrollReason), this);
+        mSensorOverlays.show(getSensorId(), getOverlayReasonFromEnrollReason(mEnrollReason),
+                this);
 
         BiometricNotificationUtils.cancelBadCalibrationNotification(getContext());
         try {
diff --git a/services/core/java/com/android/server/camera/CameraServiceProxy.java b/services/core/java/com/android/server/camera/CameraServiceProxy.java
index 11eb782..b882c47 100644
--- a/services/core/java/com/android/server/camera/CameraServiceProxy.java
+++ b/services/core/java/com/android/server/camera/CameraServiceProxy.java
@@ -841,6 +841,7 @@
                     streamProtos[i].histogramCounts = streamStats.getHistogramCounts();
                     streamProtos[i].dynamicRangeProfile = streamStats.getDynamicRangeProfile();
                     streamProtos[i].streamUseCase = streamStats.getStreamUseCase();
+                    streamProtos[i].colorSpace = streamStats.getColorSpace();
 
                     if (CameraServiceProxy.DEBUG) {
                         String histogramTypeName =
@@ -863,7 +864,8 @@
                                 + ", histogramCounts "
                                 + Arrays.toString(streamProtos[i].histogramCounts)
                                 + ", dynamicRangeProfile " + streamProtos[i].dynamicRangeProfile
-                                + ", streamUseCase " + streamProtos[i].streamUseCase);
+                                + ", streamUseCase " + streamProtos[i].streamUseCase
+                                + ", colorSpace " + streamProtos[i].colorSpace);
                     }
                 }
             }
diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java
index 45b0f0a6..6a000d9 100644
--- a/services/core/java/com/android/server/connectivity/Vpn.java
+++ b/services/core/java/com/android/server/connectivity/Vpn.java
@@ -28,6 +28,7 @@
 import static android.os.PowerWhitelistManager.REASON_VPN;
 import static android.os.UserHandle.PER_USER_RANGE;
 
+import static com.android.net.module.util.NetworkStackConstants.IPV6_MIN_MTU;
 import static com.android.server.vcn.util.PersistableBundleUtils.STRING_DESERIALIZER;
 
 import static java.util.Objects.requireNonNull;
@@ -84,6 +85,7 @@
 import android.net.VpnProfileState;
 import android.net.VpnService;
 import android.net.VpnTransportInfo;
+import android.net.ipsec.ike.ChildSaProposal;
 import android.net.ipsec.ike.ChildSessionCallback;
 import android.net.ipsec.ike.ChildSessionConfiguration;
 import android.net.ipsec.ike.ChildSessionParams;
@@ -93,6 +95,7 @@
 import android.net.ipsec.ike.IkeSessionConnectionInfo;
 import android.net.ipsec.ike.IkeSessionParams;
 import android.net.ipsec.ike.IkeTunnelConnectionParams;
+import android.net.ipsec.ike.exceptions.IkeIOException;
 import android.net.ipsec.ike.exceptions.IkeNetworkLostException;
 import android.net.ipsec.ike.exceptions.IkeNonProtocolException;
 import android.net.ipsec.ike.exceptions.IkeProtocolException;
@@ -140,6 +143,7 @@
 import com.android.server.DeviceIdleInternal;
 import com.android.server.LocalServices;
 import com.android.server.net.BaseNetworkObserver;
+import com.android.server.vcn.util.MtuUtils;
 import com.android.server.vcn.util.PersistableBundleUtils;
 
 import libcore.io.IoUtils;
@@ -152,6 +156,8 @@
 import java.net.Inet4Address;
 import java.net.Inet6Address;
 import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
 import java.net.UnknownHostException;
 import java.nio.charset.StandardCharsets;
 import java.security.GeneralSecurityException;
@@ -165,6 +171,7 @@
 import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
@@ -551,6 +558,24 @@
                 return DATA_STALL_RESET_DELAYS_SEC[count];
             }
         }
+
+        /** Gets the MTU of an interface using Java NetworkInterface primitives */
+        public int getJavaNetworkInterfaceMtu(@Nullable String iface, int defaultValue)
+                throws SocketException {
+            if (iface == null) return defaultValue;
+
+            final NetworkInterface networkInterface = NetworkInterface.getByName(iface);
+            return networkInterface == null ? defaultValue : networkInterface.getMTU();
+        }
+
+        /** Calculates the VPN Network's max MTU based on underlying network and configuration */
+        public int calculateVpnMtu(
+                @NonNull List<ChildSaProposal> childProposals,
+                int maxMtu,
+                int underlyingMtu,
+                boolean isIpv4) {
+            return MtuUtils.getMtu(childProposals, maxMtu, underlyingMtu, isIpv4);
+        }
     }
 
     @VisibleForTesting
@@ -1397,6 +1422,11 @@
     }
 
     private LinkProperties makeLinkProperties() {
+        // The design of disabling IPv6 is only enabled for IKEv2 VPN because it needs additional
+        // logic to handle IPv6 only VPN, and the IPv6 only VPN may be restarted when its MTU
+        // is lower than 1280. The logic is controlled by IKEv2VpnRunner, so the design is only
+        // enabled for IKEv2 VPN.
+        final boolean disableIPV6 = (isIkev2VpnRunner() && mConfig.mtu < IPV6_MIN_MTU);
         boolean allowIPv4 = mConfig.allowIPv4;
         boolean allowIPv6 = mConfig.allowIPv6;
 
@@ -1406,6 +1436,7 @@
 
         if (mConfig.addresses != null) {
             for (LinkAddress address : mConfig.addresses) {
+                if (disableIPV6 && address.isIpv6()) continue;
                 lp.addLinkAddress(address);
                 allowIPv4 |= address.getAddress() instanceof Inet4Address;
                 allowIPv6 |= address.getAddress() instanceof Inet6Address;
@@ -1414,8 +1445,9 @@
 
         if (mConfig.routes != null) {
             for (RouteInfo route : mConfig.routes) {
+                final InetAddress address = route.getDestination().getAddress();
+                if (disableIPV6 && address instanceof Inet6Address) continue;
                 lp.addRoute(route);
-                InetAddress address = route.getDestination().getAddress();
 
                 if (route.getType() == RouteInfo.RTN_UNICAST) {
                     allowIPv4 |= address instanceof Inet4Address;
@@ -1426,7 +1458,8 @@
 
         if (mConfig.dnsServers != null) {
             for (String dnsServer : mConfig.dnsServers) {
-                InetAddress address = InetAddresses.parseNumericAddress(dnsServer);
+                final InetAddress address = InetAddresses.parseNumericAddress(dnsServer);
+                if (disableIPV6 && address instanceof Inet6Address) continue;
                 lp.addDnsServer(address);
                 allowIPv4 |= address instanceof Inet4Address;
                 allowIPv6 |= address instanceof Inet6Address;
@@ -1440,7 +1473,7 @@
                     NetworkStackConstants.IPV4_ADDR_ANY, 0), null /*gateway*/,
                     null /*iface*/, RTN_UNREACHABLE));
         }
-        if (!allowIPv6) {
+        if (!allowIPv6 || disableIPV6) {
             lp.addRoute(new RouteInfo(new IpPrefix(
                     NetworkStackConstants.IPV6_ADDR_ANY, 0), null /*gateway*/,
                     null /*iface*/, RTN_UNREACHABLE));
@@ -1576,6 +1609,18 @@
         updateState(DetailedState.DISCONNECTED, "agentDisconnect");
     }
 
+    @GuardedBy("this")
+    private void startNewNetworkAgent(NetworkAgent oldNetworkAgent, String reason) {
+        // Initialize the state for a new agent, while keeping the old one connected
+        // in case this new connection fails.
+        mNetworkAgent = null;
+        updateState(DetailedState.CONNECTING, reason);
+        // Bringing up a new NetworkAgent to prevent the data leakage before tearing down the old
+        // NetworkAgent.
+        agentConnect();
+        agentDisconnect(oldNetworkAgent);
+    }
+
     /**
      * Establish a VPN network and return the file descriptor of the VPN interface. This methods
      * returns {@code null} if the application is revoked or not prepared.
@@ -1665,16 +1710,7 @@
                     setUnderlyingNetworks(config.underlyingNetworks);
                 }
             } else {
-                // Initialize the state for a new agent, while keeping the old one connected
-                // in case this new connection fails.
-                mNetworkAgent = null;
-                updateState(DetailedState.CONNECTING, "establish");
-                // Set up forwarding and DNS rules.
-                agentConnect();
-                // Remove the old tun's user forwarding rules
-                // The new tun's user rules have already been added above so they will take over
-                // as rules are deleted. This prevents data leakage as the rules are moved over.
-                agentDisconnect(oldNetworkAgent);
+                startNewNetworkAgent(oldNetworkAgent, "establish");
             }
 
             if (oldConnection != null) {
@@ -2711,6 +2747,17 @@
         void onSessionLost(int token, @Nullable Exception exception);
     }
 
+    private static boolean isIPv6Only(List<LinkAddress> linkAddresses) {
+        boolean hasIPV6 = false;
+        boolean hasIPV4 = false;
+        for (final LinkAddress address : linkAddresses) {
+            hasIPV6 |= address.isIpv6();
+            hasIPV4 |= address.isIpv4();
+        }
+
+        return hasIPV6 && !hasIPV4;
+    }
+
     /**
      * Internal class managing IKEv2/IPsec VPN connectivity
      *
@@ -2881,7 +2928,6 @@
                     ikeConfiguration.isIkeExtensionEnabled(
                             IkeSessionConfiguration.EXTENSION_TYPE_MOBIKE);
             onIkeConnectionInfoChanged(token, ikeConfiguration.getIkeSessionConnectionInfo());
-            mRetryCount = 0;
         }
 
         /**
@@ -2924,15 +2970,27 @@
 
             try {
                 final String interfaceName = mTunnelIface.getInterfaceName();
-                final int maxMtu = mProfile.getMaxMtu();
                 final List<LinkAddress> internalAddresses = childConfig.getInternalAddresses();
                 final List<String> dnsAddrStrings = new ArrayList<>();
+                int vpnMtu;
+                vpnMtu = calculateVpnMtu();
+
+                // If the VPN is IPv6 only and its MTU is lower than 1280, mark the network as lost
+                // and send the VpnManager event to the VPN app.
+                if (isIPv6Only(internalAddresses) && vpnMtu < IPV6_MIN_MTU) {
+                    onSessionLost(
+                            token,
+                            new IkeIOException(
+                                    new IOException("No valid addresses for MTU < 1280")));
+                    return;
+                }
 
                 final Collection<RouteInfo> newRoutes = VpnIkev2Utils.getRoutesFromTrafficSelectors(
                         childConfig.getOutboundTrafficSelectors());
                 for (final LinkAddress address : internalAddresses) {
                     mTunnelIface.addAddress(address.getAddress(), address.getPrefixLength());
                 }
+
                 for (InetAddress addr : childConfig.getInternalDnsServers()) {
                     dnsAddrStrings.add(addr.getHostAddress());
                 }
@@ -2950,7 +3008,7 @@
                     if (mVpnRunner != this) return;
 
                     mInterface = interfaceName;
-                    mConfig.mtu = maxMtu;
+                    mConfig.mtu = vpnMtu;
                     mConfig.interfaze = mInterface;
 
                     mConfig.addresses.clear();
@@ -2989,6 +3047,7 @@
                 }
 
                 doSendLinkProperties(networkAgent, lp);
+                mRetryCount = 0;
             } catch (Exception e) {
                 Log.d(TAG, "Error in ChildOpened for token " + token, e);
                 onSessionLost(token, e);
@@ -3053,12 +3112,54 @@
                     // Ignore stale runner.
                     if (mVpnRunner != this) return;
 
+                    final LinkProperties oldLp = makeLinkProperties();
+
+                    final boolean underlyingNetworkHasChanged =
+                            !Arrays.equals(mConfig.underlyingNetworks, new Network[]{network});
                     mConfig.underlyingNetworks = new Network[] {network};
-                    mNetworkCapabilities =
-                            new NetworkCapabilities.Builder(mNetworkCapabilities)
-                                    .setUnderlyingNetworks(Collections.singletonList(network))
-                                    .build();
-                    doSetUnderlyingNetworks(mNetworkAgent, Collections.singletonList(network));
+                    mConfig.mtu = calculateVpnMtu();
+
+                    final LinkProperties newLp = makeLinkProperties();
+
+                    // If MTU is < 1280, IPv6 addresses will be removed. If there are no addresses
+                    // left (e.g. IPv6-only VPN network), mark VPN as having lost the session.
+                    if (newLp.getLinkAddresses().isEmpty()) {
+                        onSessionLost(
+                                token,
+                                new IkeIOException(
+                                        new IOException("No valid addresses for MTU < 1280")));
+                        return;
+                    }
+
+                    final Set<LinkAddress> removedAddrs = new HashSet<>(oldLp.getLinkAddresses());
+                    removedAddrs.removeAll(newLp.getLinkAddresses());
+
+                    // If addresses were removed despite no IKE config change, IPv6 addresses must
+                    // have been removed due to MTU size. Restart the VPN to ensure all IPv6
+                    // unconnected sockets on the new VPN network are closed and retried on the new
+                    // VPN network.
+                    if (!removedAddrs.isEmpty()) {
+                        startNewNetworkAgent(
+                                mNetworkAgent, "MTU too low for IPv6; restarting network agent");
+
+                        for (LinkAddress removed : removedAddrs) {
+                            mTunnelIface.removeAddress(
+                                    removed.getAddress(), removed.getPrefixLength());
+                        }
+                    } else {
+                        // Put below 3 updates into else block is because agentConnect() will do
+                        // those things, so there is no need to do the redundant work.
+                        if (!newLp.equals(oldLp)) doSendLinkProperties(mNetworkAgent, newLp);
+                        if (underlyingNetworkHasChanged) {
+                            mNetworkCapabilities =
+                                    new NetworkCapabilities.Builder(mNetworkCapabilities)
+                                            .setUnderlyingNetworks(
+                                                    Collections.singletonList(network))
+                                            .build();
+                            doSetUnderlyingNetworks(mNetworkAgent,
+                                    Collections.singletonList(network));
+                        }
+                    }
                 }
 
                 mTunnelIface.setUnderlyingNetwork(network);
@@ -3108,6 +3209,60 @@
             startOrMigrateIkeSession(network);
         }
 
+        @NonNull
+        private IkeSessionParams getIkeSessionParams(@NonNull Network underlyingNetwork) {
+            final IkeTunnelConnectionParams ikeTunConnParams =
+                    mProfile.getIkeTunnelConnectionParams();
+            if (ikeTunConnParams != null) {
+                final IkeSessionParams.Builder builder =
+                        new IkeSessionParams.Builder(ikeTunConnParams.getIkeSessionParams())
+                                .setNetwork(underlyingNetwork);
+                return builder.build();
+            } else {
+                return VpnIkev2Utils.buildIkeSessionParams(mContext, mProfile, underlyingNetwork);
+            }
+        }
+
+        @NonNull
+        private ChildSessionParams getChildSessionParams() {
+            final IkeTunnelConnectionParams ikeTunConnParams =
+                    mProfile.getIkeTunnelConnectionParams();
+            if (ikeTunConnParams != null) {
+                return ikeTunConnParams.getTunnelModeChildSessionParams();
+            } else {
+                return VpnIkev2Utils.buildChildSessionParams(mProfile.getAllowedAlgorithms());
+            }
+        }
+
+        private int calculateVpnMtu() {
+            final Network underlyingNetwork = mIkeConnectionInfo.getNetwork();
+            final LinkProperties lp = mConnectivityManager.getLinkProperties(underlyingNetwork);
+            if (underlyingNetwork == null || lp == null) {
+                // Return the max MTU defined in VpnProfile as the fallback option when there is no
+                // underlying network or LinkProperties is null.
+                return mProfile.getMaxMtu();
+            }
+
+            int underlyingMtu = lp.getMtu();
+
+            // Try to get MTU from kernel if MTU is not set in LinkProperties.
+            if (underlyingMtu == 0) {
+                try {
+                    underlyingMtu = mDeps.getJavaNetworkInterfaceMtu(lp.getInterfaceName(),
+                            mProfile.getMaxMtu());
+                } catch (SocketException e) {
+                    Log.d(TAG, "Got a SocketException when getting MTU from kernel: " + e);
+                    return mProfile.getMaxMtu();
+                }
+            }
+
+            return mDeps.calculateVpnMtu(
+                    getChildSessionParams().getSaProposals(),
+                    mProfile.getMaxMtu(),
+                    underlyingMtu,
+                    mIkeConnectionInfo.getLocalAddress() instanceof Inet4Address);
+        }
+
         /**
          * Start a new IKE session.
          *
@@ -3158,24 +3313,6 @@
                 // (non-default) network, and start the new one.
                 resetIkeState();
 
-                // Get Ike options from IkeTunnelConnectionParams if it's available in the
-                // profile.
-                final IkeTunnelConnectionParams ikeTunConnParams =
-                        mProfile.getIkeTunnelConnectionParams();
-                final IkeSessionParams ikeSessionParams;
-                final ChildSessionParams childSessionParams;
-                if (ikeTunConnParams != null) {
-                    final IkeSessionParams.Builder builder = new IkeSessionParams.Builder(
-                            ikeTunConnParams.getIkeSessionParams()).setNetwork(underlyingNetwork);
-                    ikeSessionParams = builder.build();
-                    childSessionParams = ikeTunConnParams.getTunnelModeChildSessionParams();
-                } else {
-                    ikeSessionParams = VpnIkev2Utils.buildIkeSessionParams(
-                            mContext, mProfile, underlyingNetwork);
-                    childSessionParams = VpnIkev2Utils.buildChildSessionParams(
-                            mProfile.getAllowedAlgorithms());
-                }
-
                 // TODO: Remove the need for adding two unused addresses with
                 // IPsec tunnels.
                 final InetAddress address = InetAddress.getLocalHost();
@@ -3193,8 +3330,8 @@
                 mSession =
                         mIkev2SessionCreator.createIkeSession(
                                 mContext,
-                                ikeSessionParams,
-                                childSessionParams,
+                                getIkeSessionParams(underlyingNetwork),
+                                getChildSessionParams(),
                                 mExecutor,
                                 new VpnIkev2Utils.IkeSessionCallbackImpl(
                                         TAG, IkeV2VpnRunner.this, token),
@@ -3208,6 +3345,10 @@
         }
 
         private void scheduleRetryNewIkeSession() {
+            if (mScheduledHandleRetryIkeSessionFuture != null) {
+                Log.d(TAG, "There is a pending retrying task, skip the new retrying task");
+                return;
+            }
             final long retryDelay = mDeps.getNextRetryDelaySeconds(mRetryCount++);
             Log.d(TAG, "Retry new IKE session after " + retryDelay + " seconds.");
             // If the default network is lost during the retry delay, the mActiveNetwork will be
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index a3b1a42..523a2dc 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -699,7 +699,6 @@
      */
     public float getNitsFromBacklight(float backlight) {
         if (mBacklightToNitsSpline == null) {
-            Slog.wtf(TAG, "requesting nits when no mapping exists.");
             return NITS_INVALID;
         }
         backlight = Math.max(backlight, mBacklightMinimum);
diff --git a/services/core/java/com/android/server/display/DisplayModeDirector.java b/services/core/java/com/android/server/display/DisplayModeDirector.java
index aa9f2dc..726326f 100644
--- a/services/core/java/com/android/server/display/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/DisplayModeDirector.java
@@ -33,7 +33,7 @@
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManagerInternal;
 import android.hardware.display.DisplayManagerInternal.RefreshRateLimitation;
-import android.hardware.fingerprint.IUdfpsHbmListener;
+import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
 import android.net.Uri;
 import android.os.Handler;
 import android.os.IThermalEventListener;
@@ -2359,39 +2359,39 @@
         }
     }
 
-    private class UdfpsObserver extends IUdfpsHbmListener.Stub {
-        private final SparseBooleanArray mLocalHbmEnabled = new SparseBooleanArray();
+    private class UdfpsObserver extends IUdfpsRefreshRateRequestCallback.Stub {
+        private final SparseBooleanArray mUdfpsRefreshRateEnabled = new SparseBooleanArray();
 
         public void observe() {
             StatusBarManagerInternal statusBar =
                     LocalServices.getService(StatusBarManagerInternal.class);
             if (statusBar != null) {
-                statusBar.setUdfpsHbmListener(this);
+                statusBar.setUdfpsRefreshRateCallback(this);
             }
         }
 
         @Override
-        public void onHbmEnabled(int displayId) {
+        public void onRequestEnabled(int displayId) {
             synchronized (mLock) {
-                updateHbmStateLocked(displayId, true /*enabled*/);
+                updateRefreshRateStateLocked(displayId, true /*enabled*/);
             }
         }
 
         @Override
-        public void onHbmDisabled(int displayId) {
+        public void onRequestDisabled(int displayId) {
             synchronized (mLock) {
-                updateHbmStateLocked(displayId, false /*enabled*/);
+                updateRefreshRateStateLocked(displayId, false /*enabled*/);
             }
         }
 
-        private void updateHbmStateLocked(int displayId, boolean enabled) {
-            mLocalHbmEnabled.put(displayId, enabled);
+        private void updateRefreshRateStateLocked(int displayId, boolean enabled) {
+            mUdfpsRefreshRateEnabled.put(displayId, enabled);
             updateVoteLocked(displayId);
         }
 
         private void updateVoteLocked(int displayId) {
             final Vote vote;
-            if (mLocalHbmEnabled.get(displayId)) {
+            if (mUdfpsRefreshRateEnabled.get(displayId)) {
                 Display.Mode[] modes = mSupportedModesByDisplay.get(displayId);
                 float maxRefreshRate = 0f;
                 for (Display.Mode mode : modes) {
@@ -2409,10 +2409,10 @@
 
         void dumpLocked(PrintWriter pw) {
             pw.println("  UdfpsObserver");
-            pw.println("    mLocalHbmEnabled: ");
-            for (int i = 0; i < mLocalHbmEnabled.size(); i++) {
-                final int displayId = mLocalHbmEnabled.keyAt(i);
-                final String enabled = mLocalHbmEnabled.valueAt(i) ? "enabled" : "disabled";
+            pw.println("    mUdfpsRefreshRateEnabled: ");
+            for (int i = 0; i < mUdfpsRefreshRateEnabled.size(); i++) {
+                final int displayId = mUdfpsRefreshRateEnabled.keyAt(i);
+                final String enabled = mUdfpsRefreshRateEnabled.valueAt(i) ? "enabled" : "disabled";
                 pw.println("      Display " + displayId + ": " + enabled);
             }
         }
diff --git a/services/core/java/com/android/server/dreams/DreamController.java b/services/core/java/com/android/server/dreams/DreamController.java
index cd9ef09..c3313e0 100644
--- a/services/core/java/com/android/server/dreams/DreamController.java
+++ b/services/core/java/com/android/server/dreams/DreamController.java
@@ -245,6 +245,7 @@
 
                 if (mSentStartBroadcast) {
                     mContext.sendBroadcastAsUser(mDreamingStoppedIntent, UserHandle.ALL);
+                    mSentStartBroadcast = false;
                 }
 
                 mActivityTaskManager.removeRootTasksWithActivityTypes(
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index 6e2cceb..87327cb 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -23,12 +23,14 @@
 
 import static com.android.server.wm.ActivityInterceptorCallback.DREAM_MANAGER_ORDERED_ID;
 
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.TaskInfo;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -38,6 +40,8 @@
 import android.content.pm.ServiceInfo;
 import android.database.ContentObserver;
 import android.hardware.display.AmbientDisplayConfiguration;
+import android.net.Uri;
+import android.os.BatteryManager;
 import android.os.Binder;
 import android.os.Build;
 import android.os.Handler;
@@ -72,6 +76,8 @@
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
@@ -88,6 +94,15 @@
     private static final String DOZE_WAKE_LOCK_TAG = "dream:doze";
     private static final String DREAM_WAKE_LOCK_TAG = "dream:dream";
 
+    /** Constants for the when to activate dreams. */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({DREAM_ON_DOCK, DREAM_ON_CHARGE, DREAM_ON_DOCK_OR_CHARGE})
+    public @interface WhenToDream {}
+    private static final int DREAM_DISABLED = 0x0;
+    private static final int DREAM_ON_DOCK = 0x1;
+    private static final int DREAM_ON_CHARGE = 0x2;
+    private static final int DREAM_ON_DOCK_OR_CHARGE = 0x3;
+
     private final Object mLock = new Object();
 
     private final Context mContext;
@@ -101,12 +116,20 @@
     private final DreamUiEventLogger mDreamUiEventLogger;
     private final ComponentName mAmbientDisplayComponent;
     private final boolean mDismissDreamOnActivityStart;
+    private final boolean mDreamsOnlyEnabledForSystemUser;
+    private final boolean mDreamsEnabledByDefaultConfig;
+    private final boolean mDreamsActivatedOnChargeByDefault;
+    private final boolean mDreamsActivatedOnDockByDefault;
 
     @GuardedBy("mLock")
     private DreamRecord mCurrentDream;
 
     private boolean mForceAmbientDisplayEnabled;
-    private final boolean mDreamsOnlyEnabledForSystemUser;
+    private SettingsObserver mSettingsObserver;
+    private boolean mDreamsEnabledSetting;
+    @WhenToDream private int mWhenToDream;
+    private boolean mIsDocked;
+    private boolean mIsCharging;
 
     // A temporary dream component that, when present, takes precedence over user configured dream
     // component.
@@ -144,6 +167,37 @@
                 }
             };
 
+    private final BroadcastReceiver mChargingReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            mIsCharging = (BatteryManager.ACTION_CHARGING.equals(intent.getAction()));
+        }
+    };
+
+    private final BroadcastReceiver mDockStateReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            if (Intent.ACTION_DOCK_EVENT.equals(intent.getAction())) {
+                int dockState = intent.getIntExtra(Intent.EXTRA_DOCK_STATE,
+                        Intent.EXTRA_DOCK_STATE_UNDOCKED);
+                mIsDocked = dockState != Intent.EXTRA_DOCK_STATE_UNDOCKED;
+            }
+        }
+    };
+
+    private final class SettingsObserver extends ContentObserver {
+        SettingsObserver(Handler handler) {
+            super(handler);
+        }
+
+        @Override
+        public void onChange(boolean selfChange, Uri uri) {
+            synchronized (mLock) {
+                updateWhenToDreamSettings();
+            }
+        }
+    }
+
     public DreamManagerService(Context context) {
         super(context);
         mContext = context;
@@ -157,13 +211,21 @@
         mDozeConfig = new AmbientDisplayConfiguration(mContext);
         mUiEventLogger = new UiEventLoggerImpl();
         mDreamUiEventLogger = new DreamUiEventLoggerImpl(
-                mContext.getResources().getString(R.string.config_loggable_dream_prefix));
+                mContext.getResources().getStringArray(R.array.config_loggable_dream_prefixes));
         AmbientDisplayConfiguration adc = new AmbientDisplayConfiguration(mContext);
         mAmbientDisplayComponent = ComponentName.unflattenFromString(adc.ambientDisplayComponent());
         mDreamsOnlyEnabledForSystemUser =
                 mContext.getResources().getBoolean(R.bool.config_dreamsOnlyEnabledForSystemUser);
         mDismissDreamOnActivityStart = mContext.getResources().getBoolean(
                 R.bool.config_dismissDreamOnActivityStart);
+
+        mDreamsEnabledByDefaultConfig = mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_dreamsEnabledByDefault);
+        mDreamsActivatedOnChargeByDefault = mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_dreamsActivatedOnSleepByDefault);
+        mDreamsActivatedOnDockByDefault = mContext.getResources().getBoolean(
+                com.android.internal.R.bool.config_dreamsActivatedOnDockByDefault);
+        mSettingsObserver = new SettingsObserver(mHandler);
     }
 
     @Override
@@ -197,6 +259,30 @@
                         DREAM_MANAGER_ORDERED_ID,
                         mActivityInterceptorCallback);
             }
+
+            mContext.registerReceiver(
+                    mDockStateReceiver, new IntentFilter(Intent.ACTION_DOCK_EVENT));
+            IntentFilter chargingIntentFilter = new IntentFilter();
+            chargingIntentFilter.addAction(BatteryManager.ACTION_CHARGING);
+            chargingIntentFilter.addAction(BatteryManager.ACTION_DISCHARGING);
+            mContext.registerReceiver(mChargingReceiver, chargingIntentFilter);
+
+            mSettingsObserver = new SettingsObserver(mHandler);
+            mContext.getContentResolver().registerContentObserver(Settings.Secure.getUriFor(
+                            Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP),
+                    false, mSettingsObserver, UserHandle.USER_ALL);
+            mContext.getContentResolver().registerContentObserver(Settings.Secure.getUriFor(
+                            Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK),
+                    false, mSettingsObserver, UserHandle.USER_ALL);
+            mContext.getContentResolver().registerContentObserver(Settings.Secure.getUriFor(
+                            Settings.Secure.SCREENSAVER_ENABLED),
+                    false, mSettingsObserver, UserHandle.USER_ALL);
+
+            // We don't get an initial broadcast for the batter state, so we have to initialize
+            // directly from BatteryManager.
+            mIsCharging = mContext.getSystemService(BatteryManager.class).isCharging();
+
+            updateWhenToDreamSettings();
         }
     }
 
@@ -207,6 +293,14 @@
             pw.println("mCurrentDream=" + mCurrentDream);
             pw.println("mForceAmbientDisplayEnabled=" + mForceAmbientDisplayEnabled);
             pw.println("mDreamsOnlyEnabledForSystemUser=" + mDreamsOnlyEnabledForSystemUser);
+            pw.println("mDreamsEnabledSetting=" + mDreamsEnabledSetting);
+            pw.println("mForceAmbientDisplayEnabled=" + mForceAmbientDisplayEnabled);
+            pw.println("mDreamsOnlyEnabledForSystemUser=" + mDreamsOnlyEnabledForSystemUser);
+            pw.println("mDreamsActivatedOnDockByDefault=" + mDreamsActivatedOnDockByDefault);
+            pw.println("mDreamsActivatedOnChargeByDefault=" + mDreamsActivatedOnChargeByDefault);
+            pw.println("mIsDocked=" + mIsDocked);
+            pw.println("mIsCharging=" + mIsCharging);
+            pw.println("mWhenToDream=" + mWhenToDream);
             pw.println("getDozeComponent()=" + getDozeComponent());
             pw.println();
 
@@ -214,7 +308,28 @@
         }
     }
 
-    /** Whether a real dream is occurring. */
+    private void updateWhenToDreamSettings() {
+        synchronized (mLock) {
+            final ContentResolver resolver = mContext.getContentResolver();
+
+            final int activateWhenCharging = (Settings.Secure.getIntForUser(resolver,
+                    Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP,
+                    mDreamsActivatedOnChargeByDefault ? 1 : 0,
+                    UserHandle.USER_CURRENT) != 0) ? DREAM_ON_CHARGE : DREAM_DISABLED;
+            final int activateWhenDocked = (Settings.Secure.getIntForUser(resolver,
+                    Settings.Secure.SCREENSAVER_ACTIVATE_ON_DOCK,
+                    mDreamsActivatedOnDockByDefault ? 1 : 0,
+                    UserHandle.USER_CURRENT) != 0) ? DREAM_ON_DOCK : DREAM_DISABLED;
+            mWhenToDream = activateWhenCharging + activateWhenDocked;
+
+            mDreamsEnabledSetting = (Settings.Secure.getIntForUser(resolver,
+                    Settings.Secure.SCREENSAVER_ENABLED,
+                    mDreamsEnabledByDefaultConfig ? 1 : 0,
+                    UserHandle.USER_CURRENT) != 0);
+        }
+    }
+
+        /** Whether a real dream is occurring. */
     private boolean isDreamingInternal() {
         synchronized (mLock) {
             return mCurrentDream != null && !mCurrentDream.isPreview
@@ -236,6 +351,30 @@
         }
     }
 
+    /** Whether dreaming can start given user settings and the current dock/charge state. */
+    private boolean canStartDreamingInternal(boolean isScreenOn) {
+        synchronized (mLock) {
+            // Can't start dreaming if we are already dreaming.
+            if (isScreenOn && isDreamingInternal()) {
+                return false;
+            }
+
+            if (!mDreamsEnabledSetting) {
+                return false;
+            }
+
+            if ((mWhenToDream & DREAM_ON_CHARGE) == DREAM_ON_CHARGE) {
+                return mIsCharging;
+            }
+
+            if ((mWhenToDream & DREAM_ON_DOCK) == DREAM_ON_DOCK) {
+                return mIsDocked;
+            }
+
+            return false;
+        }
+    }
+
     protected void requestStartDreamFromShell() {
         requestDreamInternal();
     }
@@ -869,6 +1008,11 @@
         }
 
         @Override
+        public boolean canStartDreaming(boolean isScreenOn) {
+            return canStartDreamingInternal(isScreenOn);
+        }
+
+        @Override
         public ComponentName getActiveDreamComponent(boolean doze) {
             return getActiveDreamComponentInternal(doze);
         }
diff --git a/services/core/java/com/android/server/dreams/DreamUiEventLoggerImpl.java b/services/core/java/com/android/server/dreams/DreamUiEventLoggerImpl.java
index 26ca74a..96ebcbb 100644
--- a/services/core/java/com/android/server/dreams/DreamUiEventLoggerImpl.java
+++ b/services/core/java/com/android/server/dreams/DreamUiEventLoggerImpl.java
@@ -26,10 +26,10 @@
  * @hide
  */
 public class DreamUiEventLoggerImpl implements DreamUiEventLogger {
-    final String mLoggableDreamPrefix;
+    private final String[] mLoggableDreamPrefixes;
 
-    DreamUiEventLoggerImpl(String loggableDreamPrefix) {
-        mLoggableDreamPrefix = loggableDreamPrefix;
+    DreamUiEventLoggerImpl(String[] loggableDreamPrefixes) {
+        mLoggableDreamPrefixes = loggableDreamPrefixes;
     }
 
     @Override
@@ -38,13 +38,20 @@
         if (eventID <= 0) {
             return;
         }
-        final boolean isFirstPartyDream =
-                mLoggableDreamPrefix.isEmpty() ? false : dreamComponentName.startsWith(
-                        mLoggableDreamPrefix);
         FrameworkStatsLog.write(FrameworkStatsLog.DREAM_UI_EVENT_REPORTED,
                 /* uid = 1 */ 0,
                 /* event_id = 2 */ eventID,
                 /* instance_id = 3 */ 0,
-                /* dream_component_name = 4 */ isFirstPartyDream ? dreamComponentName : "other");
+                /* dream_component_name = 4 */
+                isFirstPartyDream(dreamComponentName) ? dreamComponentName : "other");
+    }
+
+    private boolean isFirstPartyDream(String dreamComponentName) {
+        for (int i = 0; i < mLoggableDreamPrefixes.length; ++i) {
+            if (dreamComponentName.startsWith(mLoggableDreamPrefixes[i])) {
+                return true;
+            }
+        }
+        return false;
     }
 }
diff --git a/services/core/java/com/android/server/infra/FrameworkResourcesServiceNameResolver.java b/services/core/java/com/android/server/infra/FrameworkResourcesServiceNameResolver.java
index 5253d34..d4e8f27 100644
--- a/services/core/java/com/android/server/infra/FrameworkResourcesServiceNameResolver.java
+++ b/services/core/java/com/android/server/infra/FrameworkResourcesServiceNameResolver.java
@@ -19,28 +19,9 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.StringRes;
-import android.annotation.UserIdInt;
-import android.app.AppGlobals;
-import android.content.ComponentName;
 import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.pm.ServiceInfo;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.Message;
-import android.os.SystemClock;
-import android.text.TextUtils;
-import android.util.Slog;
-import android.util.SparseArray;
-import android.util.SparseBooleanArray;
-import android.util.TimeUtils;
-
-import com.android.internal.annotations.GuardedBy;
 
 import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.List;
 
 /**
  * Gets the service name using a framework resources, temporarily changing the service if necessary
@@ -48,259 +29,42 @@
  *
  * @hide
  */
-public final class FrameworkResourcesServiceNameResolver implements ServiceNameResolver {
+public final class FrameworkResourcesServiceNameResolver extends ServiceNameBaseResolver {
 
-    private static final String TAG = FrameworkResourcesServiceNameResolver.class.getSimpleName();
-
-    /** Handler message to {@link #resetTemporaryService(int)} */
-    private static final int MSG_RESET_TEMPORARY_SERVICE = 0;
-
-    @NonNull
-    private final Context mContext;
-    @NonNull
-    private final Object mLock = new Object();
-    @StringRes
     private final int mStringResourceId;
     @ArrayRes
     private final int mArrayResourceId;
-    private final boolean mIsMultiple;
-    /**
-     * Map of temporary service name list set by {@link #setTemporaryServices(int, String[], int)},
-     * keyed by {@code userId}.
-     *
-     * <p>Typically used by Shell command and/or CTS tests to configure temporary services if
-     * mIsMultiple is true.
-     */
-    @GuardedBy("mLock")
-    private final SparseArray<String[]> mTemporaryServiceNamesList = new SparseArray<>();
-    /**
-     * Map of default services that have been disabled by
-     * {@link #setDefaultServiceEnabled(int, boolean)},keyed by {@code userId}.
-     *
-     * <p>Typically used by Shell command and/or CTS tests.
-     */
-    @GuardedBy("mLock")
-    private final SparseBooleanArray mDefaultServicesDisabled = new SparseBooleanArray();
-    @Nullable
-    private NameResolverListener mOnSetCallback;
-    /**
-     * When the temporary service will expire (and reset back to the default).
-     */
-    @GuardedBy("mLock")
-    private long mTemporaryServiceExpiration;
-
-    /**
-     * Handler used to reset the temporary service name.
-     */
-    @GuardedBy("mLock")
-    private Handler mTemporaryHandler;
 
     public FrameworkResourcesServiceNameResolver(@NonNull Context context,
             @StringRes int resourceId) {
-        mContext = context;
+        super(context, false);
         mStringResourceId = resourceId;
         mArrayResourceId = -1;
-        mIsMultiple = false;
     }
 
     public FrameworkResourcesServiceNameResolver(@NonNull Context context,
             @ArrayRes int resourceId, boolean isMultiple) {
+        super(context, isMultiple);
         if (!isMultiple) {
             throw new UnsupportedOperationException("Please use "
                     + "FrameworkResourcesServiceNameResolver(context, @StringRes int) constructor "
                     + "if single service mode is requested.");
         }
-        mContext = context;
         mStringResourceId = -1;
         mArrayResourceId = resourceId;
-        mIsMultiple = true;
     }
 
     @Override
-    public void setOnTemporaryServiceNameChangedCallback(@NonNull NameResolverListener callback) {
-        synchronized (mLock) {
-            this.mOnSetCallback = callback;
-        }
+    public String[] readServiceNameList(int userId) {
+        return mContext.getResources().getStringArray(mArrayResourceId);
     }
 
+    @Nullable
     @Override
-    public String getServiceName(@UserIdInt int userId) {
-        String[] serviceNames = getServiceNameList(userId);
-        return (serviceNames == null || serviceNames.length == 0) ? null : serviceNames[0];
+    public String readServiceName(int userId) {
+        return mContext.getResources().getString(mStringResourceId);
     }
 
-    @Override
-    public String getDefaultServiceName(@UserIdInt int userId) {
-        String[] serviceNames = getDefaultServiceNameList(userId);
-        return (serviceNames == null || serviceNames.length == 0) ? null : serviceNames[0];
-    }
-
-    /**
-     * Gets the default list of the service names for the given user.
-     *
-     * <p>Typically implemented by services which want to provide multiple backends.
-     */
-    @Override
-    public String[] getServiceNameList(int userId) {
-        synchronized (mLock) {
-            String[] temporaryNames = mTemporaryServiceNamesList.get(userId);
-            if (temporaryNames != null) {
-                // Always log it, as it should only be used on CTS or during development
-                Slog.w(TAG, "getServiceName(): using temporary name "
-                        + Arrays.toString(temporaryNames) + " for user " + userId);
-                return temporaryNames;
-            }
-            final boolean disabled = mDefaultServicesDisabled.get(userId);
-            if (disabled) {
-                // Always log it, as it should only be used on CTS or during development
-                Slog.w(TAG, "getServiceName(): temporary name not set and default disabled for "
-                        + "user " + userId);
-                return null;
-            }
-            return getDefaultServiceNameList(userId);
-
-        }
-    }
-
-    /**
-     * Gets the default list of the service names for the given user.
-     *
-     * <p>Typically implemented by services which want to provide multiple backends.
-     */
-    @Override
-    public String[] getDefaultServiceNameList(int userId) {
-        synchronized (mLock) {
-            if (mIsMultiple) {
-                String[] serviceNameList = mContext.getResources().getStringArray(mArrayResourceId);
-                // Filter out unimplemented services
-                // Initialize the validated array as null because we do not know the final size.
-                List<String> validatedServiceNameList = new ArrayList<>();
-                try {
-                    for (int i = 0; i < serviceNameList.length; i++) {
-                        if (TextUtils.isEmpty(serviceNameList[i])) {
-                            continue;
-                        }
-                        ComponentName serviceComponent = ComponentName.unflattenFromString(
-                                serviceNameList[i]);
-                        ServiceInfo serviceInfo = AppGlobals.getPackageManager().getServiceInfo(
-                                serviceComponent,
-                                PackageManager.MATCH_DIRECT_BOOT_AWARE
-                                        | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
-                        if (serviceInfo != null) {
-                            validatedServiceNameList.add(serviceNameList[i]);
-                        }
-                    }
-                } catch (Exception e) {
-                    Slog.e(TAG, "Could not validate provided services.", e);
-                }
-                String[] validatedServiceNameArray = new String[validatedServiceNameList.size()];
-                return validatedServiceNameList.toArray(validatedServiceNameArray);
-            } else {
-                final String name = mContext.getString(mStringResourceId);
-                return TextUtils.isEmpty(name) ? new String[0] : new String[]{name};
-            }
-        }
-    }
-
-    @Override
-    public boolean isConfiguredInMultipleMode() {
-        return mIsMultiple;
-    }
-
-    @Override
-    public boolean isTemporary(@UserIdInt int userId) {
-        synchronized (mLock) {
-            return mTemporaryServiceNamesList.get(userId) != null;
-        }
-    }
-
-    @Override
-    public void setTemporaryService(@UserIdInt int userId, @NonNull String componentName,
-            int durationMs) {
-        setTemporaryServices(userId, new String[]{componentName}, durationMs);
-    }
-
-    @Override
-    public void setTemporaryServices(int userId, @NonNull String[] componentNames, int durationMs) {
-        synchronized (mLock) {
-            mTemporaryServiceNamesList.put(userId, componentNames);
-
-            if (mTemporaryHandler == null) {
-                mTemporaryHandler = new Handler(Looper.getMainLooper(), null, true) {
-                    @Override
-                    public void handleMessage(Message msg) {
-                        if (msg.what == MSG_RESET_TEMPORARY_SERVICE) {
-                            synchronized (mLock) {
-                                resetTemporaryService(userId);
-                            }
-                        } else {
-                            Slog.wtf(TAG, "invalid handler msg: " + msg);
-                        }
-                    }
-                };
-            } else {
-                mTemporaryHandler.removeMessages(MSG_RESET_TEMPORARY_SERVICE);
-            }
-            mTemporaryServiceExpiration = SystemClock.elapsedRealtime() + durationMs;
-            mTemporaryHandler.sendEmptyMessageDelayed(MSG_RESET_TEMPORARY_SERVICE, durationMs);
-            for (int i = 0; i < componentNames.length; i++) {
-                notifyTemporaryServiceNameChangedLocked(userId, componentNames[i],
-                        /* isTemporary= */ true);
-            }
-        }
-    }
-
-    @Override
-    public void resetTemporaryService(@UserIdInt int userId) {
-        synchronized (mLock) {
-            Slog.i(TAG, "resetting temporary service for user " + userId + " from "
-                    + Arrays.toString(mTemporaryServiceNamesList.get(userId)));
-            mTemporaryServiceNamesList.remove(userId);
-            if (mTemporaryHandler != null) {
-                mTemporaryHandler.removeMessages(MSG_RESET_TEMPORARY_SERVICE);
-                mTemporaryHandler = null;
-            }
-            notifyTemporaryServiceNameChangedLocked(userId, /* newTemporaryName= */ null,
-                    /* isTemporary= */ false);
-        }
-    }
-
-    @Override
-    public boolean setDefaultServiceEnabled(int userId, boolean enabled) {
-        synchronized (mLock) {
-            final boolean currentlyEnabled = isDefaultServiceEnabledLocked(userId);
-            if (currentlyEnabled == enabled) {
-                Slog.i(TAG, "setDefaultServiceEnabled(" + userId + "): already " + enabled);
-                return false;
-            }
-            if (enabled) {
-                Slog.i(TAG, "disabling default service for user " + userId);
-                mDefaultServicesDisabled.removeAt(userId);
-            } else {
-                Slog.i(TAG, "enabling default service for user " + userId);
-                mDefaultServicesDisabled.put(userId, true);
-            }
-        }
-        return true;
-    }
-
-    @Override
-    public boolean isDefaultServiceEnabled(int userId) {
-        synchronized (mLock) {
-            return isDefaultServiceEnabledLocked(userId);
-        }
-    }
-
-    private boolean isDefaultServiceEnabledLocked(int userId) {
-        return !mDefaultServicesDisabled.get(userId);
-    }
-
-    @Override
-    public String toString() {
-        synchronized (mLock) {
-            return "FrameworkResourcesServiceNamer[temps=" + mTemporaryServiceNamesList + "]";
-        }
-    }
 
     // TODO(b/117779333): support proto
     @Override
@@ -314,31 +78,4 @@
             pw.print(mDefaultServicesDisabled.size());
         }
     }
-
-    // TODO(b/117779333): support proto
-    @Override
-    public void dumpShort(@NonNull PrintWriter pw, @UserIdInt int userId) {
-        synchronized (mLock) {
-            final String[] temporaryNames = mTemporaryServiceNamesList.get(userId);
-            if (temporaryNames != null) {
-                pw.print("tmpName=");
-                pw.print(Arrays.toString(temporaryNames));
-                final long ttl = mTemporaryServiceExpiration - SystemClock.elapsedRealtime();
-                pw.print(" (expires in ");
-                TimeUtils.formatDuration(ttl, pw);
-                pw.print("), ");
-            }
-            pw.print("defaultName=");
-            pw.print(getDefaultServiceName(userId));
-            final boolean disabled = mDefaultServicesDisabled.get(userId);
-            pw.println(disabled ? " (disabled)" : " (enabled)");
-        }
-    }
-
-    private void notifyTemporaryServiceNameChangedLocked(@UserIdInt int userId,
-            @Nullable String newTemporaryName, boolean isTemporary) {
-        if (mOnSetCallback != null) {
-            mOnSetCallback.onNameResolved(userId, newTemporaryName, isTemporary);
-        }
-    }
 }
diff --git a/services/core/java/com/android/server/infra/SecureSettingsServiceNameResolver.java b/services/core/java/com/android/server/infra/SecureSettingsServiceNameResolver.java
index cac7f53..17d75e6 100644
--- a/services/core/java/com/android/server/infra/SecureSettingsServiceNameResolver.java
+++ b/services/core/java/com/android/server/infra/SecureSettingsServiceNameResolver.java
@@ -19,8 +19,11 @@
 import android.annotation.UserIdInt;
 import android.content.Context;
 import android.provider.Settings;
+import android.text.TextUtils;
+import android.util.ArraySet;
 
 import java.io.PrintWriter;
+import java.util.Set;
 
 /**
  * Gets the service name using a property from the {@link android.provider.Settings.Secure}
@@ -28,21 +31,34 @@
  *
  * @hide
  */
-public final class SecureSettingsServiceNameResolver implements ServiceNameResolver {
+public final class SecureSettingsServiceNameResolver extends ServiceNameBaseResolver {
+    /**
+     * The delimiter to be used to parse the secure settings string. Services must make sure
+     * that this delimiter is used while adding component names to their secure setting property.
+     */
+    private static final char COMPONENT_NAME_SEPARATOR = ':';
 
-    private final @NonNull Context mContext;
+    private final TextUtils.SimpleStringSplitter mStringColonSplitter =
+            new TextUtils.SimpleStringSplitter(COMPONENT_NAME_SEPARATOR);
 
     @NonNull
     private final String mProperty;
 
     public SecureSettingsServiceNameResolver(@NonNull Context context, @NonNull String property) {
-        mContext = context;
-        mProperty = property;
+        this(context, property, /*isMultiple*/false);
     }
 
-    @Override
-    public String getDefaultServiceName(@UserIdInt int userId) {
-        return Settings.Secure.getStringForUser(mContext.getContentResolver(), mProperty, userId);
+    /**
+     *
+     * @param context the context required to retrieve the secure setting value
+     * @param property name of the secure setting key
+     * @param isMultiple true if the system service using this resolver needs to connect to
+     *                   multiple remote services, false otherwise
+     */
+    public SecureSettingsServiceNameResolver(@NonNull Context context, @NonNull String property,
+            boolean isMultiple) {
+        super(context, isMultiple);
+        mProperty = property;
     }
 
     // TODO(b/117779333): support proto
@@ -61,4 +77,34 @@
     public String toString() {
         return "SecureSettingsServiceNameResolver[" + mProperty + "]";
     }
+
+    @Override
+    public String[] readServiceNameList(int userId) {
+        return parseColonDelimitedServiceNames(
+                Settings.Secure.getStringForUser(
+                        mContext.getContentResolver(), mProperty, userId));
+    }
+
+    @Override
+    public String readServiceName(int userId) {
+        return Settings.Secure.getStringForUser(
+                mContext.getContentResolver(), mProperty, userId);
+    }
+
+    private String[] parseColonDelimitedServiceNames(String serviceNames) {
+        final Set<String> delimitedServices = new ArraySet<>();
+        if (!TextUtils.isEmpty(serviceNames)) {
+            final TextUtils.SimpleStringSplitter splitter = mStringColonSplitter;
+            splitter.setString(serviceNames);
+            while (splitter.hasNext()) {
+                final String str = splitter.next();
+                if (TextUtils.isEmpty(str)) {
+                    continue;
+                }
+                delimitedServices.add(str);
+            }
+        }
+        String[] delimitedServicesArray = new String[delimitedServices.size()];
+        return delimitedServices.toArray(delimitedServicesArray);
+    }
 }
diff --git a/services/core/java/com/android/server/infra/ServiceNameBaseResolver.java b/services/core/java/com/android/server/infra/ServiceNameBaseResolver.java
new file mode 100644
index 0000000..76ea05e
--- /dev/null
+++ b/services/core/java/com/android/server/infra/ServiceNameBaseResolver.java
@@ -0,0 +1,325 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.infra;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.app.AppGlobals;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.SystemClock;
+import android.text.TextUtils;
+import android.util.Slog;
+import android.util.SparseArray;
+import android.util.SparseBooleanArray;
+import android.util.TimeUtils;
+
+import com.android.internal.annotations.GuardedBy;
+
+import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Gets the service name using a framework resources, temporarily changing the service if necessary
+ * (typically during CTS tests or service development).
+ *
+ * @hide
+ */
+public abstract class ServiceNameBaseResolver implements ServiceNameResolver {
+
+    private static final String TAG = ServiceNameBaseResolver.class.getSimpleName();
+
+    /** Handler message to {@link #resetTemporaryService(int)} */
+    private static final int MSG_RESET_TEMPORARY_SERVICE = 0;
+
+    @NonNull
+    protected final Context mContext;
+    @NonNull
+    protected final Object mLock = new Object();
+
+    protected final boolean mIsMultiple;
+    /**
+     * Map of temporary service name list set by {@link #setTemporaryServices(int, String[], int)},
+     * keyed by {@code userId}.
+     *
+     * <p>Typically used by Shell command and/or CTS tests to configure temporary services if
+     * mIsMultiple is true.
+     */
+    @GuardedBy("mLock")
+    protected final SparseArray<String[]> mTemporaryServiceNamesList = new SparseArray<>();
+    /**
+     * Map of default services that have been disabled by
+     * {@link #setDefaultServiceEnabled(int, boolean)},keyed by {@code userId}.
+     *
+     * <p>Typically used by Shell command and/or CTS tests.
+     */
+    @GuardedBy("mLock")
+    protected final SparseBooleanArray mDefaultServicesDisabled = new SparseBooleanArray();
+    @Nullable
+    private NameResolverListener mOnSetCallback;
+    /**
+     * When the temporary service will expire (and reset back to the default).
+     */
+    @GuardedBy("mLock")
+    private long mTemporaryServiceExpiration;
+
+    /**
+     * Handler used to reset the temporary service name.
+     */
+    @GuardedBy("mLock")
+    private Handler mTemporaryHandler;
+
+    protected ServiceNameBaseResolver(Context context, boolean isMultiple) {
+        mContext = context;
+        mIsMultiple = isMultiple;
+    }
+
+    @Override
+    public void setOnTemporaryServiceNameChangedCallback(@NonNull NameResolverListener callback) {
+        synchronized (mLock) {
+            this.mOnSetCallback = callback;
+        }
+    }
+
+    @Override
+    public String getServiceName(@UserIdInt int userId) {
+        String[] serviceNames = getServiceNameList(userId);
+        return (serviceNames == null || serviceNames.length == 0) ? null : serviceNames[0];
+    }
+
+    @Override
+    public String getDefaultServiceName(@UserIdInt int userId) {
+        String[] serviceNames = getDefaultServiceNameList(userId);
+        return (serviceNames == null || serviceNames.length == 0) ? null : serviceNames[0];
+    }
+
+    /**
+     * Gets the default list of the service names for the given user.
+     *
+     * <p>Typically implemented by services which want to provide multiple backends.
+     */
+    @Override
+    public String[] getServiceNameList(int userId) {
+        synchronized (mLock) {
+            String[] temporaryNames = mTemporaryServiceNamesList.get(userId);
+            if (temporaryNames != null) {
+                // Always log it, as it should only be used on CTS or during development
+                Slog.w(TAG, "getServiceName(): using temporary name "
+                        + Arrays.toString(temporaryNames) + " for user " + userId);
+                return temporaryNames;
+            }
+            final boolean disabled = mDefaultServicesDisabled.get(userId);
+            if (disabled) {
+                // Always log it, as it should only be used on CTS or during development
+                Slog.w(TAG, "getServiceName(): temporary name not set and default disabled for "
+                        + "user " + userId);
+                return null;
+            }
+            return getDefaultServiceNameList(userId);
+
+        }
+    }
+
+    /**
+     * Base classes must override this to read from the desired config e.g. framework resource,
+     * secure settings etc.
+     */
+    @Nullable
+    public abstract String[] readServiceNameList(int userId);
+
+    /**
+     * Base classes must override this to read from the desired config e.g. framework resource,
+     * secure settings etc.
+     */
+    @Nullable
+    public abstract String readServiceName(int userId);
+
+    /**
+     * Gets the default list of the service names for the given user.
+     *
+     * <p>Typically implemented by services which want to provide multiple backends.
+     */
+    @Override
+    public String[] getDefaultServiceNameList(int userId) {
+        synchronized (mLock) {
+            if (mIsMultiple) {
+                String[] serviceNameList = readServiceNameList(userId);
+                // Filter out unimplemented services
+                // Initialize the validated array as null because we do not know the final size.
+                List<String> validatedServiceNameList = new ArrayList<>();
+                try {
+                    for (int i = 0; i < serviceNameList.length; i++) {
+                        if (TextUtils.isEmpty(serviceNameList[i])) {
+                            continue;
+                        }
+                        ComponentName serviceComponent = ComponentName.unflattenFromString(
+                                serviceNameList[i]);
+                        ServiceInfo serviceInfo = AppGlobals.getPackageManager().getServiceInfo(
+                                serviceComponent,
+                                PackageManager.MATCH_DIRECT_BOOT_AWARE
+                                        | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, userId);
+                        if (serviceInfo != null) {
+                            validatedServiceNameList.add(serviceNameList[i]);
+                        }
+                    }
+                } catch (Exception e) {
+                    Slog.e(TAG, "Could not validate provided services.", e);
+                }
+                String[] validatedServiceNameArray = new String[validatedServiceNameList.size()];
+                return validatedServiceNameList.toArray(validatedServiceNameArray);
+            } else {
+                final String name = readServiceName(userId);
+                return TextUtils.isEmpty(name) ? new String[0] : new String[]{name};
+            }
+        }
+    }
+
+    @Override
+    public boolean isConfiguredInMultipleMode() {
+        return mIsMultiple;
+    }
+
+    @Override
+    public boolean isTemporary(@UserIdInt int userId) {
+        synchronized (mLock) {
+            return mTemporaryServiceNamesList.get(userId) != null;
+        }
+    }
+
+    @Override
+    public void setTemporaryService(@UserIdInt int userId, @NonNull String componentName,
+            int durationMs) {
+        setTemporaryServices(userId, new String[]{componentName}, durationMs);
+    }
+
+    @Override
+    public void setTemporaryServices(int userId, @NonNull String[] componentNames, int durationMs) {
+        synchronized (mLock) {
+            mTemporaryServiceNamesList.put(userId, componentNames);
+
+            if (mTemporaryHandler == null) {
+                mTemporaryHandler = new Handler(Looper.getMainLooper(), null, true) {
+                    @Override
+                    public void handleMessage(Message msg) {
+                        if (msg.what == MSG_RESET_TEMPORARY_SERVICE) {
+                            synchronized (mLock) {
+                                resetTemporaryService(userId);
+                            }
+                        } else {
+                            Slog.wtf(TAG, "invalid handler msg: " + msg);
+                        }
+                    }
+                };
+            } else {
+                mTemporaryHandler.removeMessages(MSG_RESET_TEMPORARY_SERVICE);
+            }
+            mTemporaryServiceExpiration = SystemClock.elapsedRealtime() + durationMs;
+            mTemporaryHandler.sendEmptyMessageDelayed(MSG_RESET_TEMPORARY_SERVICE, durationMs);
+            for (int i = 0; i < componentNames.length; i++) {
+                notifyTemporaryServiceNameChangedLocked(userId, componentNames[i],
+                        /* isTemporary= */ true);
+            }
+        }
+    }
+
+    @Override
+    public void resetTemporaryService(@UserIdInt int userId) {
+        synchronized (mLock) {
+            Slog.i(TAG, "resetting temporary service for user " + userId + " from "
+                    + Arrays.toString(mTemporaryServiceNamesList.get(userId)));
+            mTemporaryServiceNamesList.remove(userId);
+            if (mTemporaryHandler != null) {
+                mTemporaryHandler.removeMessages(MSG_RESET_TEMPORARY_SERVICE);
+                mTemporaryHandler = null;
+            }
+            notifyTemporaryServiceNameChangedLocked(userId, /* newTemporaryName= */ null,
+                    /* isTemporary= */ false);
+        }
+    }
+
+    @Override
+    public boolean setDefaultServiceEnabled(int userId, boolean enabled) {
+        synchronized (mLock) {
+            final boolean currentlyEnabled = isDefaultServiceEnabledLocked(userId);
+            if (currentlyEnabled == enabled) {
+                Slog.i(TAG, "setDefaultServiceEnabled(" + userId + "): already " + enabled);
+                return false;
+            }
+            if (enabled) {
+                Slog.i(TAG, "disabling default service for user " + userId);
+                mDefaultServicesDisabled.removeAt(userId);
+            } else {
+                Slog.i(TAG, "enabling default service for user " + userId);
+                mDefaultServicesDisabled.put(userId, true);
+            }
+        }
+        return true;
+    }
+
+    @Override
+    public boolean isDefaultServiceEnabled(int userId) {
+        synchronized (mLock) {
+            return isDefaultServiceEnabledLocked(userId);
+        }
+    }
+
+    @GuardedBy("mLock")
+    private boolean isDefaultServiceEnabledLocked(int userId) {
+        return !mDefaultServicesDisabled.get(userId);
+    }
+
+    @Override
+    public String toString() {
+        synchronized (mLock) {
+            return "FrameworkResourcesServiceNamer[temps=" + mTemporaryServiceNamesList + "]";
+        }
+    }
+
+    // TODO(b/117779333): support proto
+    @Override
+    public void dumpShort(@NonNull PrintWriter pw, @UserIdInt int userId) {
+        synchronized (mLock) {
+            final String[] temporaryNames = mTemporaryServiceNamesList.get(userId);
+            if (temporaryNames != null) {
+                pw.print("tmpName=");
+                pw.print(Arrays.toString(temporaryNames));
+                final long ttl = mTemporaryServiceExpiration - SystemClock.elapsedRealtime();
+                pw.print(" (expires in ");
+                TimeUtils.formatDuration(ttl, pw);
+                pw.print("), ");
+            }
+            pw.print("defaultName=");
+            pw.print(getDefaultServiceName(userId));
+            final boolean disabled = mDefaultServicesDisabled.get(userId);
+            pw.println(disabled ? " (disabled)" : " (enabled)");
+        }
+    }
+
+    private void notifyTemporaryServiceNameChangedLocked(@UserIdInt int userId,
+            @Nullable String newTemporaryName, boolean isTemporary) {
+        if (mOnSetCallback != null) {
+            mOnSetCallback.onNameResolved(userId, newTemporaryName, isTemporary);
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/input/BatteryController.java b/services/core/java/com/android/server/input/BatteryController.java
index 36199de..9d4f181 100644
--- a/services/core/java/com/android/server/input/BatteryController.java
+++ b/services/core/java/com/android/server/input/BatteryController.java
@@ -371,6 +371,17 @@
         }
     }
 
+    public void notifyStylusGestureStarted(int deviceId, long eventTime) {
+        synchronized (mLock) {
+            final DeviceMonitor monitor = mDeviceMonitors.get(deviceId);
+            if (monitor == null) {
+                return;
+            }
+
+            monitor.onStylusGestureStarted(eventTime);
+        }
+    }
+
     public void dump(PrintWriter pw, String prefix) {
         synchronized (mLock) {
             final String indent = prefix + "  ";
@@ -557,6 +568,8 @@
 
         public void onTimeout(long eventTime) {}
 
+        public void onStylusGestureStarted(long eventTime) {}
+
         // Returns the current battery state that can be used to notify listeners BatteryController.
         public State getBatteryStateForReporting() {
             return new State(mState);
@@ -600,6 +613,22 @@
         }
 
         @Override
+        public void onStylusGestureStarted(long eventTime) {
+            processChangesAndNotify(eventTime, (time) -> {
+                final boolean wasValid = mValidityTimeoutCallback != null;
+                if (!wasValid && mState.capacity == 0.f) {
+                    // Handle a special case where the USI device reports a battery capacity of 0
+                    // at boot until the first battery update. To avoid wrongly sending out a
+                    // battery capacity of 0 if we detect stylus presence before the capacity
+                    // is first updated, do not validate the battery state when the state is not
+                    // valid and the capacity is 0.
+                    return;
+                }
+                markUsiBatteryValid();
+            });
+        }
+
+        @Override
         public void onTimeout(long eventTime) {
             processChangesAndNotify(eventTime, (time) -> markUsiBatteryInvalid());
         }
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 69b0e65..31f63d8 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -19,6 +19,8 @@
 import static android.provider.DeviceConfig.NAMESPACE_INPUT_NATIVE_BOOT;
 import static android.view.KeyEvent.KEYCODE_UNKNOWN;
 
+import android.Manifest;
+import android.annotation.EnforcePermission;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManagerInternal;
@@ -2671,6 +2673,12 @@
         mBatteryController.unregisterBatteryListener(deviceId, listener, Binder.getCallingPid());
     }
 
+    @EnforcePermission(Manifest.permission.BLUETOOTH)
+    @Override
+    public String getInputDeviceBluetoothAddress(int deviceId) {
+        return mNative.getBluetoothAddress(deviceId);
+    }
+
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;
@@ -3052,6 +3060,12 @@
                 com.android.internal.R.bool.config_perDisplayFocusEnabled);
     }
 
+    // Native callback.
+    @SuppressWarnings("unused")
+    private void notifyStylusGestureStarted(int deviceId, long eventTime) {
+        mBatteryController.notifyStylusGestureStarted(deviceId, eventTime);
+    }
+
     /**
      * Flatten a map into a string list, with value positioned directly next to the
      * key.
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index 63c0a88..cfa7fb1 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -204,6 +204,9 @@
     /** Set the displayId on which the mouse cursor should be shown. */
     void setPointerDisplayId(int displayId);
 
+    /** Get the bluetooth address of an input device if known, otherwise return null. */
+    String getBluetoothAddress(int deviceId);
+
     /** The native implementation of InputManagerService methods. */
     class NativeImpl implements NativeInputManagerService {
         /** Pointer to native input manager service object, used by native code. */
@@ -418,5 +421,8 @@
 
         @Override
         public native void setPointerDisplayId(int displayId);
+
+        @Override
+        public native String getBluetoothAddress(int deviceId);
     }
 }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index b48b7b7..fcdb1f5 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -144,6 +144,7 @@
 import android.window.ImeOnBackInvokedDispatcher;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.content.PackageMonitor;
 import com.android.internal.infra.AndroidFuture;
 import com.android.internal.inputmethod.DirectBootAwareness;
@@ -1592,8 +1593,13 @@
         private final InputMethodManagerService mService;
 
         public Lifecycle(Context context) {
+            this(context, new InputMethodManagerService(context));
+        }
+
+        public Lifecycle(
+                Context context, @NonNull InputMethodManagerService inputMethodManagerService) {
             super(context);
-            mService = new InputMethodManagerService(context);
+            mService = inputMethodManagerService;
         }
 
         @Override
@@ -1668,12 +1674,25 @@
     }
 
     public InputMethodManagerService(Context context) {
+        this(context, null, null);
+    }
+
+    @VisibleForTesting
+    InputMethodManagerService(
+            Context context,
+            @Nullable ServiceThread serviceThreadForTesting,
+            @Nullable InputMethodBindingController bindingControllerForTesting) {
         mContext = context;
         mRes = context.getResources();
         // TODO(b/196206770): Disallow I/O on this thread. Currently it's needed for loading
         // additional subtypes in switchUserOnHandlerLocked().
-        final ServiceThread thread = new ServiceThread(
-                HANDLER_THREAD_NAME, Process.THREAD_PRIORITY_FOREGROUND, true /* allowIo */);
+        final ServiceThread thread =
+                serviceThreadForTesting != null
+                        ? serviceThreadForTesting
+                        : new ServiceThread(
+                                HANDLER_THREAD_NAME,
+                                Process.THREAD_PRIORITY_FOREGROUND,
+                                true /* allowIo */);
         thread.start();
         mHandler = Handler.createAsync(thread.getLooper(), this);
         // Note: SettingsObserver doesn't register observers in its constructor.
@@ -1701,10 +1720,13 @@
 
         updateCurrentProfileIds();
         AdditionalSubtypeUtils.load(mAdditionalSubtypeMap, userId);
-        mSwitchingController = InputMethodSubtypeSwitchingController.createInstanceLocked(
-                mSettings, context);
+        mSwitchingController =
+                InputMethodSubtypeSwitchingController.createInstanceLocked(mSettings, context);
         mMenuController = new InputMethodMenuController(this);
-        mBindingController = new InputMethodBindingController(this);
+        mBindingController =
+                bindingControllerForTesting != null
+                        ? bindingControllerForTesting
+                        : new InputMethodBindingController(this);
         mAutofillController = new AutofillSuggestionsController(this);
         mPreventImeStartupUnlessTextEditor = mRes.getBoolean(
                 com.android.internal.R.bool.config_preventImeStartupUnlessTextEditor);
@@ -3673,6 +3695,7 @@
         // UI for input.
         if (isTextEditor && editorInfo != null
                 && shouldRestoreImeVisibility(windowToken, softInputMode)) {
+            if (DEBUG) Slog.v(TAG, "Will show input to restore visibility");
             res = startInputUncheckedLocked(cs, inputContext, remoteAccessibilityInputConnection,
                     editorInfo, startInputFlags, startInputReason, unverifiedTargetSdkVersion,
                     imeDispatcher);
@@ -3719,11 +3742,17 @@
                                 imeDispatcher);
                         didStart = true;
                     }
-                    showCurrentInputLocked(windowToken, InputMethodManager.SHOW_IMPLICIT, null,
+                    showCurrentInputLocked(
+                            windowToken,
+                            InputMethodManager.SHOW_IMPLICIT,
+                            null,
                             SoftInputShowHideReason.SHOW_AUTO_EDITOR_FORWARD_NAV);
                 }
                 break;
             case LayoutParams.SOFT_INPUT_STATE_UNCHANGED:
+                if (DEBUG) {
+                    Slog.v(TAG, "Window asks to keep the input in whatever state it was last in");
+                }
                 // Do nothing.
                 break;
             case LayoutParams.SOFT_INPUT_STATE_HIDDEN:
@@ -3794,6 +3823,7 @@
                     // To maintain compatibility, we are now hiding the IME when we don't have
                     // an editor upon refocusing a window.
                     if (startInputByWinGainedFocus) {
+                        if (DEBUG) Slog.v(TAG, "Same window without editor will hide input");
                         hideCurrentInputLocked(mCurFocusedWindow, 0, null,
                                 SoftInputShowHideReason.HIDE_SAME_WINDOW_FOCUSED_WITHOUT_EDITOR);
                     }
@@ -3807,6 +3837,7 @@
                     // 1) SOFT_INPUT_STATE_UNCHANGED state without an editor
                     // 2) SOFT_INPUT_STATE_VISIBLE state without an editor
                     // 3) SOFT_INPUT_STATE_ALWAYS_VISIBLE state without an editor
+                    if (DEBUG) Slog.v(TAG, "Window without editor will hide input");
                     hideCurrentInputLocked(mCurFocusedWindow, 0, null,
                             SoftInputShowHideReason.HIDE_WINDOW_GAINED_FOCUS_WITHOUT_EDITOR);
                 }
@@ -3894,8 +3925,7 @@
 
     @EnforcePermission(Manifest.permission.WRITE_SECURE_SETTINGS)
     @Override
-    public void showInputMethodPickerFromSystem(IInputMethodClient client, int auxiliarySubtypeMode,
-            int displayId) {
+    public void showInputMethodPickerFromSystem(int auxiliarySubtypeMode, int displayId) {
         // Always call subtype picker, because subtype picker is a superset of input method
         // picker.
         super.showInputMethodPickerFromSystem_enforcePermission();
@@ -4388,7 +4418,7 @@
      * a stylus deviceId is not already registered on device.
      */
     @BinderThread
-    @EnforcePermission(Manifest.permission.INJECT_EVENTS)
+    @EnforcePermission(Manifest.permission.TEST_INPUT_METHOD)
     @Override
     public void addVirtualStylusIdForTestSession(IInputMethodClient client) {
         super.addVirtualStylusIdForTestSession_enforcePermission();
@@ -4615,12 +4645,6 @@
             }
             if (!setVisible) {
                 if (mCurClient != null) {
-                    // IMMS only knows of focused window, not the actual IME target.
-                    // e.g. it isn't aware of any window that has both
-                    // NOT_FOCUSABLE, ALT_FOCUSABLE_IM flags set and can the IME target.
-                    // Send it to window manager to hide IME from IME target window.
-                    // TODO(b/139861270): send to mCurClient.client once IMMS is aware of
-                    // actual IME target.
                     mWindowManagerInternal.hideIme(
                             mHideRequestWindowMap.get(windowToken),
                             mCurClient.mSelfReportedDisplayId);
diff --git a/services/core/java/com/android/server/locales/LocaleManagerService.java b/services/core/java/com/android/server/locales/LocaleManagerService.java
index 364f6db..4ce0320 100644
--- a/services/core/java/com/android/server/locales/LocaleManagerService.java
+++ b/services/core/java/com/android/server/locales/LocaleManagerService.java
@@ -364,16 +364,19 @@
         // 1.) A normal, non-privileged app querying its own locale.
         // 2.) The installer of the given app querying locales of a package installed by said
         // installer.
-        // 3.) The current input method querying locales of another package.
+        // 3.) The current input method querying locales of the current foreground app.
         // 4.) A privileged system service querying locales of another package.
         // The least privileged case is a normal app performing a query, so check that first and get
         // locales if the package name is owned by the app. Next check if the calling app is the
         // installer of the given app and get locales. Finally check if the calling app is the
-        // current input method. If neither conditions matched, check if the caller has the
-        // necessary permission and fetch locales.
+        // current input method, and that app is querying locales of the current foreground app. If
+        // neither conditions matched, check if the caller has the necessary permission and fetch
+        // locales.
         if (!isPackageOwnedByCaller(appPackageName, userId)
                 && !isCallerInstaller(appPackageName, userId)
-                && !isCallerFromCurrentInputMethod(userId)) {
+                && !(isCallerFromCurrentInputMethod(userId)
+                    && mActivityManagerInternal.isAppForeground(
+                            getPackageUid(appPackageName, userId)))) {
             enforceReadAppSpecificLocalesPermission();
         }
         final long token = Binder.clearCallingIdentity();
diff --git a/services/core/java/com/android/server/location/contexthub/ContextHubService.java b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
index 4f63ebc..90245b5e 100644
--- a/services/core/java/com/android/server/location/contexthub/ContextHubService.java
+++ b/services/core/java/com/android/server/location/contexthub/ContextHubService.java
@@ -129,7 +129,7 @@
             new RemoteCallbackList<>();
 
     // Proxy object to communicate with the Context Hub HAL
-    private IContextHubWrapper mContextHubWrapper;
+    private final IContextHubWrapper mContextHubWrapper;
 
     // The manager for transaction queue
     private ContextHubTransactionManager mTransactionManager;
@@ -207,11 +207,35 @@
             handleClientMessageCallback(mContextHubId, hostEndpointId, message, nanoappPermissions,
                     messagePermissions);
         }
+
+        @Override
+        public void handleServiceRestart() {
+            Log.i(TAG, "Starting Context Hub Service restart");
+            initExistingCallbacks();
+            resetSettings();
+            Log.i(TAG, "Finished Context Hub Service restart");
+        }
     }
 
     public ContextHubService(Context context, IContextHubWrapper contextHubWrapper) {
+        Log.i(TAG, "Starting Context Hub Service init");
         mContext = context;
-        init(contextHubWrapper, /* isFirstInit= */ true);
+        long startTimeNs = SystemClock.elapsedRealtimeNanos();
+        mContextHubWrapper = contextHubWrapper;
+        if (!initContextHubServiceState(startTimeNs)) {
+            Log.e(TAG, "Failed to initialize the Context Hub Service");
+            return;
+        }
+        initDefaultClientMap();
+
+        initLocationSettingNotifications();
+        initWifiSettingNotifications();
+        initAirplaneModeSettingNotifications();
+        initMicrophoneSettingNotifications();
+        initBtSettingNotifications();
+
+        scheduleDailyMetricSnapshot();
+        Log.i(TAG, "Finished Context Hub Service init");
     }
 
     /**
@@ -293,11 +317,10 @@
      * Initializes the private state of the ContextHubService
      *
      * @param startTimeNs               the start time when init was called
-     * @param isFirstInit               if true, this is the first time init is called - boot time
      *
      * @return      if mContextHubWrapper is not null and a full state init was done
      */
-    private boolean initContextHubServiceState(long startTimeNs, boolean isFirstInit) {
+    private boolean initContextHubServiceState(long startTimeNs) {
         if (mContextHubWrapper == null) {
             mTransactionManager = null;
             mClientManager = null;
@@ -317,12 +340,10 @@
             hubInfo = new Pair(Collections.emptyList(), Collections.emptyList());
         }
 
-        if (isFirstInit) {
-            long bootTimeNs = SystemClock.elapsedRealtimeNanos() - startTimeNs;
-            int numContextHubs = hubInfo.first.size();
-            ContextHubStatsLog.write(ContextHubStatsLog.CONTEXT_HUB_BOOTED, bootTimeNs,
-                    numContextHubs);
-        }
+        long bootTimeNs = SystemClock.elapsedRealtimeNanos() - startTimeNs;
+        int numContextHubs = hubInfo.first.size();
+        ContextHubStatsLog.write(ContextHubStatsLog.CONTEXT_HUB_BOOTED, bootTimeNs,
+                numContextHubs);
 
         mContextHubIdToInfoMap = Collections.unmodifiableMap(
                 ContextHubServiceUtil.createContextHubInfoMap(hubInfo.first));
@@ -368,6 +389,20 @@
     }
 
     /**
+     * Initializes existing callbacks with the mContextHubWrapper for every context hub
+     */
+    private void initExistingCallbacks() {
+        for (int contextHubId : mContextHubIdToInfoMap.keySet()) {
+            try {
+                mContextHubWrapper.registerExistingCallback(contextHubId);
+            } catch (RemoteException e) {
+                Log.e(TAG, "RemoteException while registering existing service callback for hub "
+                        + "(ID = " + contextHubId + ")", e);
+            }
+        }
+    }
+
+    /**
      * Handles the initialization of location settings notifications
      */
     private void initLocationSettingNotifications() {
@@ -495,6 +530,17 @@
         mContext.registerReceiver(btReceiver, filter);
     }
 
+    /**
+     * Resets the settings. Called when a context hub restarts or the AIDL HAL dies
+     */
+    private void resetSettings() {
+        sendLocationSettingUpdate();
+        sendWifiSettingUpdate(/* forceUpdate= */ true);
+        sendAirplaneModeSettingUpdate();
+        sendMicrophoneDisableSettingUpdateForCurrentUser();
+        sendBtSettingUpdate(/* forceUpdate= */ true);
+    }
+
     @Override
     public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err,
             String[] args, ShellCallback callback, ResultReceiver result) {
@@ -767,31 +813,6 @@
     }
 
     /**
-     * Handles a service restart or service init for the first time
-     *
-     * @param contextHubWrapper         the Context Hub wrapper
-     * @param isFirstInit               if true, this is the first time init is called - boot time
-     */
-    private void init(IContextHubWrapper contextHubWrapper, boolean isFirstInit) {
-        Log.i(TAG, "Starting Context Hub Service init");
-        long startTimeNs = SystemClock.elapsedRealtimeNanos();
-        mContextHubWrapper = contextHubWrapper;
-        if (!initContextHubServiceState(startTimeNs, isFirstInit)) {
-            Log.e(TAG, "Failed to initialize the Context Hub Service");
-            return;
-        }
-        initDefaultClientMap();
-
-        initLocationSettingNotifications();
-        initWifiSettingNotifications();
-        initAirplaneModeSettingNotifications();
-        initMicrophoneSettingNotifications();
-        initBtSettingNotifications();
-
-        scheduleDailyMetricSnapshot();
-    }
-
-    /**
      * Handles a unicast or broadcast message from a nanoapp.
      *
      * @param contextHubId the ID of the hub the message came from
@@ -871,11 +892,7 @@
 
             ContextHubEventLogger.getInstance().logContextHubRestart(contextHubId);
 
-            sendLocationSettingUpdate();
-            sendWifiSettingUpdate(/* forceUpdate= */ true);
-            sendAirplaneModeSettingUpdate();
-            sendMicrophoneDisableSettingUpdateForCurrentUser();
-            sendBtSettingUpdate(/* forceUpdate= */ true);
+            resetSettings();
 
             mTransactionManager.onHubReset();
             queryNanoAppsInternal(contextHubId);
diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
index 432b097..48152b4 100644
--- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
+++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
@@ -32,6 +32,7 @@
 import android.hardware.location.NanoAppState;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.os.IBinder;
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
@@ -92,6 +93,11 @@
          */
         void handleNanoappMessage(short hostEndpointId, NanoAppMessage message,
                 List<String> nanoappPermissions, List<String> messagePermissions);
+
+        /**
+         * Handles a restart of the service
+         */
+        void handleServiceRestart();
     }
 
     /**
@@ -170,12 +176,9 @@
     }
 
     /**
-     * Attempts to connect to the Contexthub HAL AIDL service, if it exists.
-     *
-     * @return A valid IContextHubWrapper if the connection was successful, null otherwise.
+     * Attempts to connect to the AIDL HAL and returns the proxy IContextHub.
      */
-    @Nullable
-    public static IContextHubWrapper maybeConnectToAidl() {
+    public static android.hardware.contexthub.IContextHub maybeConnectToAidlGetProxy() {
         android.hardware.contexthub.IContextHub proxy = null;
         final String aidlServiceName =
                 android.hardware.contexthub.IContextHub.class.getCanonicalName() + "/default";
@@ -188,8 +191,18 @@
         } else {
             Log.d(TAG, "Context Hub AIDL service is not declared");
         }
+        return proxy;
+    }
 
-        return (proxy == null) ? null : new ContextHubWrapperAidl(proxy);
+    /**
+     * Attempts to connect to the Contexthub HAL AIDL service, if it exists.
+     *
+     * @return A valid IContextHubWrapper if the connection was successful, null otherwise.
+     */
+    @Nullable
+    public static IContextHubWrapper maybeConnectToAidl() {
+        android.hardware.contexthub.IContextHub proxy = maybeConnectToAidlGetProxy();
+        return proxy == null ? null : new ContextHubWrapperAidl(proxy);
     }
 
     /**
@@ -354,12 +367,22 @@
     public abstract void registerCallback(int contextHubId, @NonNull ICallback callback)
             throws RemoteException;
 
-    private static class ContextHubWrapperAidl extends IContextHubWrapper {
+    /**
+     * Registers an existing callback with the Context Hub.
+     *
+     * @param contextHubId The ID of the Context Hub to register the callback with.
+     */
+    public abstract void registerExistingCallback(int contextHubId) throws RemoteException;
+
+    private static class ContextHubWrapperAidl extends IContextHubWrapper
+            implements IBinder.DeathRecipient {
         private android.hardware.contexthub.IContextHub mHub;
 
         private final Map<Integer, ContextHubAidlCallback> mAidlCallbackMap =
                     new HashMap<>();
 
+        private Runnable mHandleServiceRestartCallback = null;
+
         // Use this thread in case where the execution requires to be on a service thread.
         // For instance, AppOpsManager.noteOp requires the UPDATE_APP_OPS_STATS permission.
         private HandlerThread mHandlerThread =
@@ -419,17 +442,51 @@
         }
 
         ContextHubWrapperAidl(android.hardware.contexthub.IContextHub hub) {
-            mHub = hub;
+            setHub(hub);
             mHandlerThread.start();
             mHandler = new Handler(mHandlerThread.getLooper());
+            linkWrapperToHubDeath();
+        }
+
+        private synchronized android.hardware.contexthub.IContextHub getHub() {
+            return mHub;
+        }
+
+        private synchronized void setHub(android.hardware.contexthub.IContextHub hub) {
+            mHub = hub;
+        }
+
+        @Override
+        public void binderDied() {
+            Log.i(TAG, "Context Hub AIDL HAL died");
+
+            setHub(maybeConnectToAidlGetProxy());
+            if (getHub() == null) {
+                // TODO(b/256860015): Make this reconnection more robust
+                Log.e(TAG, "Could not reconnect to Context Hub AIDL HAL");
+                return;
+            }
+            linkWrapperToHubDeath();
+
+            if (mHandleServiceRestartCallback != null) {
+                mHandleServiceRestartCallback.run();
+            } else {
+                Log.e(TAG, "mHandleServiceRestartCallback is not set");
+            }
         }
 
         public Pair<List<ContextHubInfo>, List<String>> getHubs() throws RemoteException {
+            android.hardware.contexthub.IContextHub hub = getHub();
+            if (hub == null) {
+                return new Pair<List<ContextHubInfo>, List<String>>(new ArrayList<ContextHubInfo>(),
+                        new ArrayList<String>());
+            }
+
             Set<String> supportedPermissions = new HashSet<>();
             ArrayList<ContextHubInfo> hubInfoList = new ArrayList<>();
-            for (android.hardware.contexthub.ContextHubInfo hub : mHub.getContextHubs()) {
-                hubInfoList.add(new ContextHubInfo(hub));
-                for (String permission : hub.supportedPermissions) {
+            for (android.hardware.contexthub.ContextHubInfo hubInfo : hub.getContextHubs()) {
+                hubInfoList.add(new ContextHubInfo(hubInfo));
+                for (String permission : hubInfo.supportedPermissions) {
                     supportedPermissions.add(permission);
                 }
             }
@@ -489,8 +546,13 @@
 
         @Override
         public void onHostEndpointConnected(HostEndpointInfo info) {
+            android.hardware.contexthub.IContextHub hub = getHub();
+            if (hub == null) {
+                return;
+            }
+
             try {
-                mHub.onHostEndpointConnected(info);
+                hub.onHostEndpointConnected(info);
             } catch (RemoteException | ServiceSpecificException e) {
                 Log.e(TAG, "Exception in onHostEndpointConnected" + e.getMessage());
             }
@@ -498,8 +560,13 @@
 
         @Override
         public void onHostEndpointDisconnected(short hostEndpointId) {
+            android.hardware.contexthub.IContextHub hub = getHub();
+            if (hub == null) {
+                return;
+            }
+
             try {
-                mHub.onHostEndpointDisconnected((char) hostEndpointId);
+                hub.onHostEndpointDisconnected((char) hostEndpointId);
             } catch (RemoteException | ServiceSpecificException e) {
                 Log.e(TAG, "Exception in onHostEndpointDisconnected" + e.getMessage());
             }
@@ -509,8 +576,13 @@
         public int sendMessageToContextHub(
                 short hostEndpointId, int contextHubId, NanoAppMessage message)
                 throws RemoteException {
+            android.hardware.contexthub.IContextHub hub = getHub();
+            if (hub == null) {
+                return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
+            }
+
             try {
-                mHub.sendMessageToHub(contextHubId,
+                hub.sendMessageToHub(contextHubId,
                         ContextHubServiceUtil.createAidlContextHubMessage(hostEndpointId, message));
                 return ContextHubTransaction.RESULT_SUCCESS;
             } catch (RemoteException | ServiceSpecificException e) {
@@ -523,10 +595,15 @@
         @ContextHubTransaction.Result
         public int loadNanoapp(int contextHubId, NanoAppBinary binary,
                 int transactionId) throws RemoteException {
+            android.hardware.contexthub.IContextHub hub = getHub();
+            if (hub == null) {
+                return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
+            }
+
             android.hardware.contexthub.NanoappBinary aidlNanoAppBinary =
                     ContextHubServiceUtil.createAidlNanoAppBinary(binary);
             try {
-                mHub.loadNanoapp(contextHubId, aidlNanoAppBinary, transactionId);
+                hub.loadNanoapp(contextHubId, aidlNanoAppBinary, transactionId);
                 return ContextHubTransaction.RESULT_SUCCESS;
             } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) {
                 return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
@@ -538,8 +615,13 @@
         @ContextHubTransaction.Result
         public int unloadNanoapp(int contextHubId, long nanoappId, int transactionId)
                 throws RemoteException {
+            android.hardware.contexthub.IContextHub hub = getHub();
+            if (hub == null) {
+                return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
+            }
+
             try {
-                mHub.unloadNanoapp(contextHubId, nanoappId, transactionId);
+                hub.unloadNanoapp(contextHubId, nanoappId, transactionId);
                 return ContextHubTransaction.RESULT_SUCCESS;
             } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) {
                 return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
@@ -551,8 +633,13 @@
         @ContextHubTransaction.Result
         public int enableNanoapp(int contextHubId, long nanoappId, int transactionId)
                 throws RemoteException {
+            android.hardware.contexthub.IContextHub hub = getHub();
+            if (hub == null) {
+                return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
+            }
+
             try {
-                mHub.enableNanoapp(contextHubId, nanoappId, transactionId);
+                hub.enableNanoapp(contextHubId, nanoappId, transactionId);
                 return ContextHubTransaction.RESULT_SUCCESS;
             } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) {
                 return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
@@ -564,8 +651,13 @@
         @ContextHubTransaction.Result
         public int disableNanoapp(int contextHubId, long nanoappId, int transactionId)
                 throws RemoteException {
+            android.hardware.contexthub.IContextHub hub = getHub();
+            if (hub == null) {
+                return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
+            }
+
             try {
-                mHub.disableNanoapp(contextHubId, nanoappId, transactionId);
+                hub.disableNanoapp(contextHubId, nanoappId, transactionId);
                 return ContextHubTransaction.RESULT_SUCCESS;
             } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) {
                 return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
@@ -576,8 +668,13 @@
 
         @ContextHubTransaction.Result
         public int queryNanoapps(int contextHubId) throws RemoteException {
+            android.hardware.contexthub.IContextHub hub = getHub();
+            if (hub == null) {
+                return ContextHubTransaction.RESULT_FAILED_BAD_PARAMS;
+            }
+
             try {
-                mHub.queryNanoapps(contextHubId);
+                hub.queryNanoapps(contextHubId);
                 return ContextHubTransaction.RESULT_SUCCESS;
             } catch (RemoteException | ServiceSpecificException | UnsupportedOperationException e) {
                 return ContextHubTransaction.RESULT_FAILED_UNKNOWN;
@@ -586,22 +683,65 @@
             }
         }
 
-        public void registerCallback(int contextHubId, ICallback callback) throws RemoteException {
-            mAidlCallbackMap.put(contextHubId, new ContextHubAidlCallback(contextHubId, callback));
+        public void registerExistingCallback(int contextHubId) {
+            android.hardware.contexthub.IContextHub hub = getHub();
+            if (hub == null) {
+                return;
+            }
+
+            ContextHubAidlCallback callback = mAidlCallbackMap.get(contextHubId);
+            if (callback == null) {
+                Log.e(TAG, "Could not find existing callback to register for context hub ID = "
+                        + contextHubId);
+                return;
+            }
+
             try {
-                mHub.registerCallback(contextHubId, mAidlCallbackMap.get(contextHubId));
+                hub.registerCallback(contextHubId, callback);
             } catch (RemoteException | ServiceSpecificException | IllegalArgumentException e) {
                 Log.e(TAG, "Exception while registering callback: " + e.getMessage());
             }
         }
 
+        public void registerCallback(int contextHubId, ICallback callback) {
+            android.hardware.contexthub.IContextHub hub = getHub();
+            if (hub == null) {
+                return;
+            }
+
+            mHandleServiceRestartCallback = callback::handleServiceRestart;
+            mAidlCallbackMap.put(contextHubId, new ContextHubAidlCallback(contextHubId, callback));
+            registerExistingCallback(contextHubId);
+        }
+
         private void onSettingChanged(byte setting, boolean enabled) {
+            android.hardware.contexthub.IContextHub hub = getHub();
+            if (hub == null) {
+                return;
+            }
+
             try {
-                mHub.onSettingChanged(setting, enabled);
+                hub.onSettingChanged(setting, enabled);
             } catch (RemoteException | ServiceSpecificException e) {
                 Log.e(TAG, "Exception while sending setting update: " + e.getMessage());
             }
         }
+
+        /**
+         * Links the mHub death handler to this
+         */
+        private void linkWrapperToHubDeath() {
+            android.hardware.contexthub.IContextHub hub = getHub();
+            if (hub == null) {
+                return;
+            }
+
+            try {
+                hub.asBinder().linkToDeath(this, 0);
+            } catch (RemoteException exception) {
+                Log.e(TAG, "Context Hub AIDL service death receipt could not be linked");
+            }
+        }
     }
 
     /**
@@ -729,6 +869,17 @@
             mHub.registerCallback(contextHubId, mHidlCallbackMap.get(contextHubId));
         }
 
+        public void registerExistingCallback(int contextHubId) throws RemoteException {
+            ContextHubWrapperHidlCallback callback = mHidlCallbackMap.get(contextHubId);
+            if (callback == null) {
+                Log.e(TAG, "Could not find existing callback for context hub with ID = "
+                        + contextHubId);
+                return;
+            }
+
+            mHub.registerCallback(contextHubId, callback);
+        }
+
         public boolean supportsBtSettingNotifications() {
             return false;
         }
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index f653b93..f5ec880 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -131,8 +131,6 @@
                             UserHandler::updateDiscoveryPreferenceOnHandler, userHandler));
                 }
             }
-
-            mEventLogger.log(new EventLogger.StringEvent("mScreenOnOffReceiver", null));
         }
     };
 
@@ -151,7 +149,7 @@
         mContext.registerReceiver(mScreenOnOffReceiver, screenOnOffIntentFilter);
     }
 
-    // Methods that implement MediaRouter2 operations.
+    // Start of methods that implement MediaRouter2 operations.
 
     @NonNull
     public void enforceMediaContentControlPermission() {
@@ -199,46 +197,6 @@
         }
     }
 
-    @NonNull
-    public RoutingSessionInfo getSystemSessionInfo(
-            @Nullable String packageName, boolean setDeviceRouteSelected) {
-        final int uid = Binder.getCallingUid();
-        final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
-        final boolean hasModifyAudioRoutingPermission = mContext.checkCallingOrSelfPermission(
-                android.Manifest.permission.MODIFY_AUDIO_ROUTING)
-                == PackageManager.PERMISSION_GRANTED;
-
-        final long token = Binder.clearCallingIdentity();
-        try {
-            RoutingSessionInfo systemSessionInfo = null;
-            synchronized (mLock) {
-                UserRecord userRecord = getOrCreateUserRecordLocked(userId);
-                List<RoutingSessionInfo> sessionInfos;
-                if (hasModifyAudioRoutingPermission) {
-                    if (setDeviceRouteSelected) {
-                        systemSessionInfo = userRecord.mHandler.mSystemProvider
-                                .generateDeviceRouteSelectedSessionInfo(packageName);
-                    } else {
-                        sessionInfos = userRecord.mHandler.mSystemProvider.getSessionInfos();
-                        if (sessionInfos != null && !sessionInfos.isEmpty()) {
-                            systemSessionInfo = new RoutingSessionInfo.Builder(sessionInfos.get(0))
-                                    .setClientPackageName(packageName).build();
-                        } else {
-                            Slog.w(TAG, "System provider does not have any session info.");
-                        }
-                    }
-                } else {
-                    systemSessionInfo = new RoutingSessionInfo.Builder(
-                            userRecord.mHandler.mSystemProvider.getDefaultSessionInfo())
-                            .setClientPackageName(packageName).build();
-                }
-            }
-            return systemSessionInfo;
-        } finally {
-            Binder.restoreCallingIdentity(token);
-        }
-    }
-
     public void registerRouter2(@NonNull IMediaRouter2 router, @NonNull String packageName) {
         Objects.requireNonNull(router, "router must not be null");
         if (TextUtils.isEmpty(packageName)) {
@@ -418,7 +376,9 @@
         }
     }
 
-    // Methods that implement MediaRouter2Manager operations.
+    // End of methods that implement MediaRouter2 operations.
+
+    // Start of methods that implement MediaRouter2Manager operations.
 
     @NonNull
     public List<RoutingSessionInfo> getRemoteSessions(@NonNull IMediaRouter2Manager manager) {
@@ -610,6 +570,52 @@
         }
     }
 
+    // End of methods that implement MediaRouter2Manager operations.
+
+    // Start of methods that implements operations for both MediaRouter2 and MediaRouter2Manager.
+
+    @NonNull
+    public RoutingSessionInfo getSystemSessionInfo(
+            @Nullable String packageName, boolean setDeviceRouteSelected) {
+        final int uid = Binder.getCallingUid();
+        final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
+        final boolean hasModifyAudioRoutingPermission = mContext.checkCallingOrSelfPermission(
+                android.Manifest.permission.MODIFY_AUDIO_ROUTING)
+                == PackageManager.PERMISSION_GRANTED;
+
+        final long token = Binder.clearCallingIdentity();
+        try {
+            RoutingSessionInfo systemSessionInfo = null;
+            synchronized (mLock) {
+                UserRecord userRecord = getOrCreateUserRecordLocked(userId);
+                List<RoutingSessionInfo> sessionInfos;
+                if (hasModifyAudioRoutingPermission) {
+                    if (setDeviceRouteSelected) {
+                        systemSessionInfo = userRecord.mHandler.mSystemProvider
+                                .generateDeviceRouteSelectedSessionInfo(packageName);
+                    } else {
+                        sessionInfos = userRecord.mHandler.mSystemProvider.getSessionInfos();
+                        if (sessionInfos != null && !sessionInfos.isEmpty()) {
+                            systemSessionInfo = new RoutingSessionInfo.Builder(sessionInfos.get(0))
+                                    .setClientPackageName(packageName).build();
+                        } else {
+                            Slog.w(TAG, "System provider does not have any session info.");
+                        }
+                    }
+                } else {
+                    systemSessionInfo = new RoutingSessionInfo.Builder(
+                            userRecord.mHandler.mSystemProvider.getDefaultSessionInfo())
+                            .setClientPackageName(packageName).build();
+                }
+            }
+            return systemSessionInfo;
+        } finally {
+            Binder.restoreCallingIdentity(token);
+        }
+    }
+
+    // End of methods that implements operations for both MediaRouter2 and MediaRouter2Manager.
+
     public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
         pw.println(prefix + "MediaRouter2ServiceImpl");
 
@@ -634,7 +640,7 @@
     /* package */ void updateRunningUserAndProfiles(int newActiveUserId) {
         synchronized (mLock) {
             if (mCurrentActiveUserId != newActiveUserId) {
-                mEventLogger.log(
+                mEventLogger.enqueue(
                         EventLogger.StringEvent.from("switchUser",
                                 "userId: %d", newActiveUserId));
 
@@ -678,6 +684,8 @@
         return mUserManagerInternal.getProfileParentId(userId) == mCurrentActiveUserId;
     }
 
+    // Start of locked methods that are used by MediaRouter2.
+
     @GuardedBy("mLock")
     private void registerRouter2Locked(@NonNull IMediaRouter2 router, int uid, int pid,
             @NonNull String packageName, int userId, boolean hasConfigureWifiDisplayPermission,
@@ -705,7 +713,7 @@
                 obtainMessage(UserHandler::notifyRouterRegistered,
                         userRecord.mHandler, routerRecord));
 
-        mEventLogger.log(EventLogger.StringEvent.from("registerRouter2",
+        mEventLogger.enqueue(EventLogger.StringEvent.from("registerRouter2",
                 "package: %s, uid: %d, pid: %d, router id: %d",
                 packageName, uid, pid, routerRecord.mRouterId));
     }
@@ -718,7 +726,7 @@
             return;
         }
 
-        mEventLogger.log(
+        mEventLogger.enqueue(
                 EventLogger.StringEvent.from(
                         "unregisterRouter2",
                         "package: %s, router id: %d",
@@ -744,7 +752,7 @@
             return;
         }
 
-        mEventLogger.log(EventLogger.StringEvent.from(
+        mEventLogger.enqueue(EventLogger.StringEvent.from(
                 "setDiscoveryRequestWithRouter2",
                 "router id: %d, discovery request: %s",
                 routerRecord.mRouterId, discoveryRequest.toString()));
@@ -766,7 +774,7 @@
         RouterRecord routerRecord = mAllRouterRecords.get(binder);
 
         if (routerRecord != null) {
-            mEventLogger.log(EventLogger.StringEvent.from(
+            mEventLogger.enqueue(EventLogger.StringEvent.from(
                     "setRouteVolumeWithRouter2",
                     "router id: %d, volume: %d",
                     routerRecord.mRouterId, volume));
@@ -851,7 +859,7 @@
             return;
         }
 
-        mEventLogger.log(EventLogger.StringEvent.from(
+        mEventLogger.enqueue(EventLogger.StringEvent.from(
                 "selectRouteWithRouter2",
                 "router id: %d, route: %s",
                 routerRecord.mRouterId, route.getId()));
@@ -871,7 +879,7 @@
             return;
         }
 
-        mEventLogger.log(EventLogger.StringEvent.from(
+        mEventLogger.enqueue(EventLogger.StringEvent.from(
                 "deselectRouteWithRouter2",
                 "router id: %d, route: %s",
                 routerRecord.mRouterId, route.getId()));
@@ -891,7 +899,7 @@
             return;
         }
 
-        mEventLogger.log(EventLogger.StringEvent.from(
+        mEventLogger.enqueue(EventLogger.StringEvent.from(
                 "transferToRouteWithRouter2",
                 "router id: %d, route: %s",
                 routerRecord.mRouterId, route.getId()));
@@ -921,7 +929,7 @@
             return;
         }
 
-        mEventLogger.log(EventLogger.StringEvent.from(
+        mEventLogger.enqueue(EventLogger.StringEvent.from(
                 "setSessionVolumeWithRouter2",
                 "router id: %d, session: %s, volume: %d",
                 routerRecord.mRouterId,  uniqueSessionId, volume));
@@ -941,7 +949,7 @@
             return;
         }
 
-        mEventLogger.log(EventLogger.StringEvent.from(
+        mEventLogger.enqueue(EventLogger.StringEvent.from(
                 "releaseSessionWithRouter2",
                 "router id: %d, session: %s",
                 routerRecord.mRouterId,  uniqueSessionId));
@@ -952,6 +960,10 @@
                         DUMMY_REQUEST_ID, routerRecord, uniqueSessionId));
     }
 
+    // End of locked methods that are used by MediaRouter2.
+
+    // Start of locked methods that are used by MediaRouter2Manager.
+
     private List<RoutingSessionInfo> getRemoteSessionsLocked(
             @NonNull IMediaRouter2Manager manager) {
         final IBinder binder = manager.asBinder();
@@ -983,7 +995,7 @@
             return;
         }
 
-        mEventLogger.log(
+        mEventLogger.enqueue(
                 EventLogger.StringEvent.from("registerManager",
                         "uid: %d, pid: %d, package: %s, userId: %d",
                         uid, pid, packageName, userId));
@@ -1025,7 +1037,7 @@
         }
         UserRecord userRecord = managerRecord.mUserRecord;
 
-        mEventLogger.log(
+        mEventLogger.enqueue(
                 EventLogger.StringEvent.from(
                         "unregisterManager",
                         "package: %s, userId: %d, managerId: %d",
@@ -1045,7 +1057,7 @@
             return;
         }
 
-        mEventLogger.log(
+        mEventLogger.enqueue(
                 EventLogger.StringEvent.from("startScan",
                         "manager: %d", managerRecord.mManagerId));
 
@@ -1059,7 +1071,7 @@
             return;
         }
 
-        mEventLogger.log(
+        mEventLogger.enqueue(
                 EventLogger.StringEvent.from("stopScan",
                         "manager: %d", managerRecord.mManagerId));
 
@@ -1076,7 +1088,7 @@
             return;
         }
 
-        mEventLogger.log(
+        mEventLogger.enqueue(
                 EventLogger.StringEvent.from("setRouteVolumeWithManager",
                         "managerId: %d, routeId: %s, volume: %d",
                         managerRecord.mManagerId, route.getId(), volume));
@@ -1096,7 +1108,7 @@
             return;
         }
 
-        mEventLogger.log(
+        mEventLogger.enqueue(
                 EventLogger.StringEvent.from("requestCreateSessionWithManager",
                         "managerId: %d, routeId: %s",
                         managerRecord.mManagerId, route.getId()));
@@ -1146,7 +1158,7 @@
             return;
         }
 
-        mEventLogger.log(
+        mEventLogger.enqueue(
                 EventLogger.StringEvent.from("selectRouteWithManager",
                         "managerId: %d, session: %s, routeId: %s",
                         managerRecord.mManagerId, uniqueSessionId, route.getId()));
@@ -1172,7 +1184,7 @@
             return;
         }
 
-        mEventLogger.log(
+        mEventLogger.enqueue(
                 EventLogger.StringEvent.from("deselectRouteWithManager",
                         "managerId: %d, session: %s, routeId: %s",
                         managerRecord.mManagerId, uniqueSessionId, route.getId()));
@@ -1198,7 +1210,7 @@
             return;
         }
 
-        mEventLogger.log(
+        mEventLogger.enqueue(
                 EventLogger.StringEvent.from("transferToRouteWithManager",
                         "managerId: %d, session: %s, routeId: %s",
                         managerRecord.mManagerId, uniqueSessionId, route.getId()));
@@ -1224,7 +1236,7 @@
             return;
         }
 
-        mEventLogger.log(
+        mEventLogger.enqueue(
                 EventLogger.StringEvent.from("setSessionVolumeWithManager",
                         "managerId: %d, session: %s, volume: %d",
                         managerRecord.mManagerId, uniqueSessionId, volume));
@@ -1245,7 +1257,7 @@
             return;
         }
 
-        mEventLogger.log(
+        mEventLogger.enqueue(
                 EventLogger.StringEvent.from("releaseSessionWithManager",
                         "managerId: %d, session: %s",
                         managerRecord.mManagerId, uniqueSessionId));
@@ -1260,6 +1272,10 @@
                         uniqueRequestId, routerRecord, uniqueSessionId));
     }
 
+    // End of locked methods that are used by MediaRouter2Manager.
+
+    // Start of locked methods that are used by both MediaRouter2 and MediaRouter2Manager.
+
     @GuardedBy("mLock")
     private UserRecord getOrCreateUserRecordLocked(int userId) {
         UserRecord userRecord = mUserRecords.get(userId);
@@ -1294,6 +1310,8 @@
         }
     }
 
+    // End of locked methods that are used by both MediaRouter2 and MediaRouter2Manager.
+
     static long toUniqueRequestId(int requesterId, int originalRequestId) {
         return ((long) requesterId << 32) | originalRequestId;
     }
diff --git a/services/core/java/com/android/server/om/OverlayManagerService.java b/services/core/java/com/android/server/om/OverlayManagerService.java
index baa471c..3421eb7 100644
--- a/services/core/java/com/android/server/om/OverlayManagerService.java
+++ b/services/core/java/com/android/server/om/OverlayManagerService.java
@@ -55,10 +55,12 @@
 import android.content.pm.IPackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.UserInfo;
+import android.content.pm.UserPackage;
 import android.content.pm.overlay.OverlayPaths;
 import android.content.res.ApkAssets;
 import android.net.Uri;
 import android.os.Binder;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Environment;
 import android.os.FabricatedOverlayInternal;
@@ -880,14 +882,14 @@
                     }
                     Slog.d(TAG, "commit failed: " + e.getMessage(), e);
                     throw new SecurityException("commit failed"
-                            + (DEBUG ? ": " + e.getMessage() : ""));
+                            + (DEBUG || Build.IS_DEBUGGABLE ? ": " + e.getMessage() : ""));
                 }
             } finally {
                 traceEnd(TRACE_TAG_RRO);
             }
         }
 
-        private Set<PackageAndUser> executeRequest(
+        private Set<UserPackage> executeRequest(
                 @NonNull final OverlayManagerTransaction.Request request)
                 throws OperationFailedException {
             Objects.requireNonNull(request, "Transaction contains a null request");
@@ -932,7 +934,7 @@
             try {
                 switch (request.type) {
                     case TYPE_SET_ENABLED:
-                        Set<PackageAndUser> result = null;
+                        Set<UserPackage> result = null;
                         result = CollectionUtils.addAll(result,
                                 mImpl.setEnabled(request.overlay, true, realUserId));
                         result = CollectionUtils.addAll(result,
@@ -973,7 +975,7 @@
 
             synchronized (mLock) {
                 // execute the requests (as calling user)
-                Set<PackageAndUser> affectedPackagesToUpdate = null;
+                Set<UserPackage> affectedPackagesToUpdate = null;
                 for (final OverlayManagerTransaction.Request request : transaction) {
                     affectedPackagesToUpdate = CollectionUtils.addAll(affectedPackagesToUpdate,
                             executeRequest(request));
@@ -1370,13 +1372,13 @@
         }
     }
 
-    private void updateTargetPackagesLocked(@Nullable PackageAndUser updatedTarget) {
+    private void updateTargetPackagesLocked(@Nullable UserPackage updatedTarget) {
         if (updatedTarget != null) {
             updateTargetPackagesLocked(Set.of(updatedTarget));
         }
     }
 
-    private void updateTargetPackagesLocked(@Nullable Set<PackageAndUser> updatedTargets) {
+    private void updateTargetPackagesLocked(@Nullable Set<UserPackage> updatedTargets) {
         if (CollectionUtils.isEmpty(updatedTargets)) {
             return;
         }
@@ -1405,7 +1407,7 @@
 
     @Nullable
     private static SparseArray<ArraySet<String>> groupTargetsByUserId(
-            @Nullable final Set<PackageAndUser> targetsAndUsers) {
+            @Nullable final Set<UserPackage> targetsAndUsers) {
         final SparseArray<ArraySet<String>> userTargets = new SparseArray<>();
         CollectionUtils.forEach(targetsAndUsers, target -> {
             ArraySet<String> targets = userTargets.get(target.userId);
@@ -1472,7 +1474,7 @@
 
     @NonNull
     private SparseArray<List<String>> updatePackageManagerLocked(
-            @Nullable Set<PackageAndUser> targets) {
+            @Nullable Set<UserPackage> targets) {
         if (CollectionUtils.isEmpty(targets)) {
             return new SparseArray<>();
         }
diff --git a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
index 17bb39c..6ffe60d 100644
--- a/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
+++ b/services/core/java/com/android/server/om/OverlayManagerServiceImpl.java
@@ -35,6 +35,7 @@
 import android.content.om.CriticalOverlayInfo;
 import android.content.om.OverlayIdentifier;
 import android.content.om.OverlayInfo;
+import android.content.pm.UserPackage;
 import android.content.pm.overlay.OverlayPaths;
 import android.content.pm.parsing.FrameworkParsingPackageUtils;
 import android.os.FabricatedOverlayInfo;
@@ -154,13 +155,13 @@
      * set of targets that had, but no longer have, active overlays.
      */
     @NonNull
-    ArraySet<PackageAndUser> updateOverlaysForUser(final int newUserId) {
+    ArraySet<UserPackage> updateOverlaysForUser(final int newUserId) {
         if (DEBUG) {
             Slog.d(TAG, "updateOverlaysForUser newUserId=" + newUserId);
         }
 
         // Remove the settings of all overlays that are no longer installed for this user.
-        final ArraySet<PackageAndUser> updatedTargets = new ArraySet<>();
+        final ArraySet<UserPackage> updatedTargets = new ArraySet<>();
         final ArrayMap<String, AndroidPackage> userPackages = mPackageManager.initializeForUser(
                 newUserId);
         CollectionUtils.addAll(updatedTargets, removeOverlaysForUser(
@@ -185,7 +186,7 @@
                 // When a new user is switched to for the first time, package manager must be
                 // informed of the overlay paths for all overlaid packages installed in the user.
                 if (overlaidByOthers.contains(pkg.getPackageName())) {
-                    updatedTargets.add(new PackageAndUser(pkg.getPackageName(), newUserId));
+                    updatedTargets.add(UserPackage.of(newUserId, pkg.getPackageName()));
                 }
             } catch (OperationFailedException e) {
                 Slog.e(TAG, "failed to initialize overlays of '" + pkg.getPackageName()
@@ -237,7 +238,7 @@
                     mSettings.setEnabled(overlay, newUserId, true);
                     if (updateState(oi, newUserId, 0)) {
                         CollectionUtils.add(updatedTargets,
-                                new PackageAndUser(oi.targetPackageName, oi.userId));
+                                UserPackage.of(oi.userId, oi.targetPackageName));
                     }
                 }
             } catch (OverlayManagerSettings.BadKeyException e) {
@@ -258,40 +259,40 @@
     }
 
     @NonNull
-    Set<PackageAndUser> onPackageAdded(@NonNull final String pkgName,
+    Set<UserPackage> onPackageAdded(@NonNull final String pkgName,
             final int userId) throws OperationFailedException {
-        final Set<PackageAndUser> updatedTargets = new ArraySet<>();
+        final Set<UserPackage> updatedTargets = new ArraySet<>();
         // Always update the overlays of newly added packages.
-        updatedTargets.add(new PackageAndUser(pkgName, userId));
+        updatedTargets.add(UserPackage.of(userId, pkgName));
         updatedTargets.addAll(reconcileSettingsForPackage(pkgName, userId, 0 /* flags */));
         return updatedTargets;
     }
 
     @NonNull
-    Set<PackageAndUser> onPackageChanged(@NonNull final String pkgName,
+    Set<UserPackage> onPackageChanged(@NonNull final String pkgName,
             final int userId) throws OperationFailedException {
         return reconcileSettingsForPackage(pkgName, userId, 0 /* flags */);
     }
 
     @NonNull
-    Set<PackageAndUser> onPackageReplacing(@NonNull final String pkgName, final int userId)
+    Set<UserPackage> onPackageReplacing(@NonNull final String pkgName, final int userId)
             throws OperationFailedException {
         return reconcileSettingsForPackage(pkgName, userId, FLAG_OVERLAY_IS_BEING_REPLACED);
     }
 
     @NonNull
-    Set<PackageAndUser> onPackageReplaced(@NonNull final String pkgName, final int userId)
+    Set<UserPackage> onPackageReplaced(@NonNull final String pkgName, final int userId)
             throws OperationFailedException {
         return reconcileSettingsForPackage(pkgName, userId, 0 /* flags */);
     }
 
     @NonNull
-    Set<PackageAndUser> onPackageRemoved(@NonNull final String pkgName, final int userId) {
+    Set<UserPackage> onPackageRemoved(@NonNull final String pkgName, final int userId) {
         if (DEBUG) {
             Slog.d(TAG, "onPackageRemoved pkgName=" + pkgName + " userId=" + userId);
         }
         // Update the state of all overlays that target this package.
-        final Set<PackageAndUser> targets = updateOverlaysForTarget(pkgName, userId, 0 /* flags */);
+        final Set<UserPackage> targets = updateOverlaysForTarget(pkgName, userId, 0 /* flags */);
 
         // Remove all the overlays this package declares.
         return CollectionUtils.addAll(targets,
@@ -299,15 +300,15 @@
     }
 
     @NonNull
-    private Set<PackageAndUser> removeOverlaysForUser(
+    private Set<UserPackage> removeOverlaysForUser(
             @NonNull final Predicate<OverlayInfo> condition, final int userId) {
         final List<OverlayInfo> overlays = mSettings.removeIf(
                 io -> userId == io.userId && condition.test(io));
-        Set<PackageAndUser> targets = Collections.emptySet();
+        Set<UserPackage> targets = Collections.emptySet();
         for (int i = 0, n = overlays.size(); i < n; i++) {
             final OverlayInfo info = overlays.get(i);
             targets = CollectionUtils.add(targets,
-                    new PackageAndUser(info.targetPackageName, userId));
+                    UserPackage.of(userId, info.targetPackageName));
 
             // Remove the idmap if the overlay is no longer installed for any user.
             removeIdmapIfPossible(info);
@@ -316,7 +317,7 @@
     }
 
     @NonNull
-    private Set<PackageAndUser> updateOverlaysForTarget(@NonNull final String targetPackage,
+    private Set<UserPackage> updateOverlaysForTarget(@NonNull final String targetPackage,
             final int userId, final int flags) {
         boolean modified = false;
         final List<OverlayInfo> overlays = mSettings.getOverlaysForTarget(targetPackage, userId);
@@ -332,18 +333,18 @@
         if (!modified) {
             return Collections.emptySet();
         }
-        return Set.of(new PackageAndUser(targetPackage, userId));
+        return Set.of(UserPackage.of(userId, targetPackage));
     }
 
     @NonNull
-    private Set<PackageAndUser> updatePackageOverlays(@NonNull AndroidPackage pkg,
+    private Set<UserPackage> updatePackageOverlays(@NonNull AndroidPackage pkg,
             final int userId, final int flags) throws OperationFailedException {
         if (pkg.getOverlayTarget() == null) {
             // This package does not have overlays declared in its manifest.
             return Collections.emptySet();
         }
 
-        Set<PackageAndUser> updatedTargets = Collections.emptySet();
+        Set<UserPackage> updatedTargets = Collections.emptySet();
         final OverlayIdentifier overlay = new OverlayIdentifier(pkg.getPackageName());
         final int priority = getPackageConfiguredPriority(pkg);
         try {
@@ -353,7 +354,7 @@
                     // If the targetPackageName has changed, the package that *used* to
                     // be the target must also update its assets.
                     updatedTargets = CollectionUtils.add(updatedTargets,
-                            new PackageAndUser(currentInfo.targetPackageName, userId));
+                            UserPackage.of(userId, currentInfo.targetPackageName));
                 }
 
                 currentInfo = mSettings.init(overlay, userId, pkg.getOverlayTarget(),
@@ -367,13 +368,13 @@
                 // reinitialized. Reorder the overlay and update its target package.
                 mSettings.setPriority(overlay, userId, priority);
                 updatedTargets = CollectionUtils.add(updatedTargets,
-                        new PackageAndUser(currentInfo.targetPackageName, userId));
+                        UserPackage.of(userId, currentInfo.targetPackageName));
             }
 
             // Update the enabled state of the overlay.
             if (updateState(currentInfo, userId, flags)) {
                 updatedTargets = CollectionUtils.add(updatedTargets,
-                        new PackageAndUser(currentInfo.targetPackageName, userId));
+                        UserPackage.of(userId, currentInfo.targetPackageName));
             }
         } catch (OverlayManagerSettings.BadKeyException e) {
             throw new OperationFailedException("failed to update settings", e);
@@ -382,14 +383,14 @@
     }
 
     @NonNull
-    private Set<PackageAndUser> reconcileSettingsForPackage(@NonNull final String pkgName,
+    private Set<UserPackage> reconcileSettingsForPackage(@NonNull final String pkgName,
             final int userId, final int flags) throws OperationFailedException {
         if (DEBUG) {
             Slog.d(TAG, "reconcileSettingsForPackage pkgName=" + pkgName + " userId=" + userId);
         }
 
         // Update the state of overlays that target this package.
-        Set<PackageAndUser> updatedTargets = Collections.emptySet();
+        Set<UserPackage> updatedTargets = Collections.emptySet();
         updatedTargets = CollectionUtils.addAll(updatedTargets,
                 updateOverlaysForTarget(pkgName, userId, flags));
 
@@ -423,7 +424,7 @@
     }
 
     @NonNull
-    Set<PackageAndUser> setEnabled(@NonNull final OverlayIdentifier overlay,
+    Set<UserPackage> setEnabled(@NonNull final OverlayIdentifier overlay,
             final boolean enable, final int userId) throws OperationFailedException {
         if (DEBUG) {
             Slog.d(TAG, String.format("setEnabled overlay=%s enable=%s userId=%d",
@@ -442,7 +443,7 @@
             modified |= updateState(oi, userId, 0);
 
             if (modified) {
-                return Set.of(new PackageAndUser(oi.targetPackageName, userId));
+                return Set.of(UserPackage.of(userId, oi.targetPackageName));
             }
             return Set.of();
         } catch (OverlayManagerSettings.BadKeyException e) {
@@ -450,7 +451,7 @@
         }
     }
 
-    Optional<PackageAndUser> setEnabledExclusive(@NonNull final OverlayIdentifier overlay,
+    Optional<UserPackage> setEnabledExclusive(@NonNull final OverlayIdentifier overlay,
             boolean withinCategory, final int userId) throws OperationFailedException {
         if (DEBUG) {
             Slog.d(TAG, String.format("setEnabledExclusive overlay=%s"
@@ -493,7 +494,7 @@
             modified |= updateState(enabledInfo, userId, 0);
 
             if (modified) {
-                return Optional.of(new PackageAndUser(enabledInfo.targetPackageName, userId));
+                return Optional.of(UserPackage.of(userId, enabledInfo.targetPackageName));
             }
             return Optional.empty();
         } catch (OverlayManagerSettings.BadKeyException e) {
@@ -502,7 +503,7 @@
     }
 
     @NonNull
-    Set<PackageAndUser> registerFabricatedOverlay(
+    Set<UserPackage> registerFabricatedOverlay(
             @NonNull final FabricatedOverlayInternal overlay)
             throws OperationFailedException {
         if (FrameworkParsingPackageUtils.validateName(overlay.overlayName,
@@ -516,7 +517,7 @@
             throw new OperationFailedException("failed to create fabricated overlay");
         }
 
-        final Set<PackageAndUser> updatedTargets = new ArraySet<>();
+        final Set<UserPackage> updatedTargets = new ArraySet<>();
         for (int userId : mSettings.getUsers()) {
             updatedTargets.addAll(registerFabricatedOverlay(info, userId));
         }
@@ -524,13 +525,13 @@
     }
 
     @NonNull
-    private Set<PackageAndUser> registerFabricatedOverlay(
+    private Set<UserPackage> registerFabricatedOverlay(
             @NonNull final FabricatedOverlayInfo info, int userId)
             throws OperationFailedException {
         final OverlayIdentifier overlayIdentifier = new OverlayIdentifier(
                 info.packageName, info.overlayName);
 
-        final Set<PackageAndUser> updatedTargets = new ArraySet<>();
+        final Set<UserPackage> updatedTargets = new ArraySet<>();
         OverlayInfo oi = mSettings.getNullableOverlayInfo(overlayIdentifier, userId);
         if (oi != null) {
             if (!oi.isFabricated) {
@@ -543,7 +544,7 @@
                 if (oi != null) {
                     // If the fabricated overlay changes its target package, update the previous
                     // target package so it no longer is overlaid.
-                    updatedTargets.add(new PackageAndUser(oi.targetPackageName, userId));
+                    updatedTargets.add(UserPackage.of(userId, oi.targetPackageName));
                 }
                 oi = mSettings.init(overlayIdentifier, userId, info.targetPackageName,
                         info.targetOverlayable, info.path, true, false,
@@ -554,7 +555,7 @@
                 mSettings.setBaseCodePath(overlayIdentifier, userId, info.path);
             }
             if (updateState(oi, userId, 0)) {
-                updatedTargets.add(new PackageAndUser(oi.targetPackageName, userId));
+                updatedTargets.add(UserPackage.of(userId, oi.targetPackageName));
             }
         } catch (OverlayManagerSettings.BadKeyException e) {
             throw new OperationFailedException("failed to update settings", e);
@@ -564,8 +565,8 @@
     }
 
     @NonNull
-    Set<PackageAndUser> unregisterFabricatedOverlay(@NonNull final OverlayIdentifier overlay) {
-        final Set<PackageAndUser> updatedTargets = new ArraySet<>();
+    Set<UserPackage> unregisterFabricatedOverlay(@NonNull final OverlayIdentifier overlay) {
+        final Set<UserPackage> updatedTargets = new ArraySet<>();
         for (int userId : mSettings.getUsers()) {
             updatedTargets.addAll(unregisterFabricatedOverlay(overlay, userId));
         }
@@ -573,7 +574,7 @@
     }
 
     @NonNull
-    private Set<PackageAndUser> unregisterFabricatedOverlay(
+    private Set<UserPackage> unregisterFabricatedOverlay(
             @NonNull final OverlayIdentifier overlay, int userId) {
         final OverlayInfo oi = mSettings.getNullableOverlayInfo(overlay, userId);
         if (oi != null) {
@@ -581,7 +582,7 @@
             if (oi.isEnabled()) {
                 // Removing a fabricated overlay only changes the overlay path of a package if it is
                 // currently enabled.
-                return Set.of(new PackageAndUser(oi.targetPackageName, userId));
+                return Set.of(UserPackage.of(userId, oi.targetPackageName));
             }
         }
         return Set.of();
@@ -627,7 +628,7 @@
         return mOverlayConfig.isEnabled(overlay.getPackageName());
     }
 
-    Optional<PackageAndUser> setPriority(@NonNull final OverlayIdentifier overlay,
+    Optional<UserPackage> setPriority(@NonNull final OverlayIdentifier overlay,
             @NonNull final OverlayIdentifier newParentOverlay, final int userId)
             throws OperationFailedException {
         try {
@@ -644,7 +645,7 @@
             }
 
             if (mSettings.setPriority(overlay, newParentOverlay, userId)) {
-                return Optional.of(new PackageAndUser(overlayInfo.targetPackageName, userId));
+                return Optional.of(UserPackage.of(userId, overlayInfo.targetPackageName));
             }
             return Optional.empty();
         } catch (OverlayManagerSettings.BadKeyException e) {
@@ -652,7 +653,7 @@
         }
     }
 
-    Set<PackageAndUser> setHighestPriority(@NonNull final OverlayIdentifier overlay,
+    Set<UserPackage> setHighestPriority(@NonNull final OverlayIdentifier overlay,
             final int userId) throws OperationFailedException {
         try{
             if (DEBUG) {
@@ -667,7 +668,7 @@
             }
 
             if (mSettings.setHighestPriority(overlay, userId)) {
-                return Set.of(new PackageAndUser(overlayInfo.targetPackageName, userId));
+                return Set.of(UserPackage.of(userId, overlayInfo.targetPackageName));
             }
             return Set.of();
         } catch (OverlayManagerSettings.BadKeyException e) {
@@ -675,7 +676,7 @@
         }
     }
 
-    Optional<PackageAndUser> setLowestPriority(@NonNull final OverlayIdentifier overlay,
+    Optional<UserPackage> setLowestPriority(@NonNull final OverlayIdentifier overlay,
             final int userId) throws OperationFailedException {
         try{
             if (DEBUG) {
@@ -690,7 +691,7 @@
             }
 
             if (mSettings.setLowestPriority(overlay, userId)) {
-                return Optional.of(new PackageAndUser(overlayInfo.targetPackageName, userId));
+                return Optional.of(UserPackage.of(userId, overlayInfo.targetPackageName));
             }
             return Optional.empty();
         } catch (OverlayManagerSettings.BadKeyException e) {
diff --git a/services/core/java/com/android/server/pm/ApexPackageInfo.java b/services/core/java/com/android/server/pm/ApexPackageInfo.java
deleted file mode 100644
index 672ae2e..0000000
--- a/services/core/java/com/android/server/pm/ApexPackageInfo.java
+++ /dev/null
@@ -1,419 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.pm;
-
-import static com.android.server.pm.ApexManager.MATCH_ACTIVE_PACKAGE;
-import static com.android.server.pm.ApexManager.MATCH_FACTORY_PACKAGE;
-
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.apex.ApexInfo;
-import android.content.pm.PackageManager;
-import android.util.ArrayMap;
-import android.util.Pair;
-import android.util.PrintWriterPrinter;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.IndentingPrintWriter;
-import com.android.internal.util.Preconditions;
-import com.android.server.pm.parsing.PackageParser2;
-import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
-import com.android.server.pm.parsing.pkg.ParsedPackage;
-import com.android.server.pm.pkg.AndroidPackage;
-import com.android.server.pm.pkg.PackageStateInternal;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
-
-import java.io.File;
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashSet;
-import java.util.List;
-import java.util.concurrent.ExecutorService;
-
-/**
- * A temporary holder to store PackageInfo for scanned apex packages. We will unify the scan/install
- * flows of APK and APEX and PMS will be the only source of truth for all package information
- * including both APK and APEX. This class will no longer be needed when the migration is done.
- */
-class ApexPackageInfo {
-    public static final boolean ENABLE_FEATURE_SCAN_APEX = true;
-
-    private static final String TAG = "ApexManager";
-    private static final String VNDK_APEX_MODULE_NAME_PREFIX = "com.android.vndk.";
-
-    private final Object mLock = new Object();
-
-    @GuardedBy("mLock")
-    private List<Pair<ApexInfo, AndroidPackage>> mAllPackagesCache;
-
-    @Nullable
-    private final PackageManagerService mPackageManager;
-
-    ApexPackageInfo() {
-        mPackageManager = null;
-    }
-
-    ApexPackageInfo(@NonNull PackageManagerService pms) {
-        mPackageManager = pms;
-    }
-
-    /**
-     * Called by package manager service to scan apex package files when device boots up.
-     *
-     * @param allPackages All apex packages to scan.
-     * @param packageParser The package parser to support apex package parsing and caching parsed
-     *                      results.
-     * @param executorService An executor to support parallel package parsing.
-     */
-    List<ApexManager.ScanResult> scanApexPackages(ApexInfo[] allPackages,
-            @NonNull PackageParser2 packageParser, @NonNull ExecutorService executorService) {
-        synchronized (mLock) {
-            return scanApexPackagesInternalLocked(allPackages, packageParser, executorService);
-        }
-    }
-
-    void notifyScanResult(List<ApexManager.ScanResult> scanResults) {
-        synchronized (mLock) {
-            notifyScanResultLocked(scanResults);
-        }
-    }
-
-    /**
-     * Retrieves information about an APEX package.
-     *
-     * @param packageName the package name to look for. Note that this is the package name reported
-     *                    in the APK container manifest (i.e. AndroidManifest.xml), which might
-     *                    differ from the one reported in the APEX manifest (i.e.
-     *                    apex_manifest.json).
-     * @param flags the type of package to return. This may match to active packages
-     *              and factory (pre-installed) packages.
-     * @return a PackageInfo object with the information about the package, or null if the package
-     *         is not found.
-     */
-    @Nullable
-    Pair<ApexInfo, AndroidPackage> getPackageInfo(String packageName,
-            @ApexManager.PackageInfoFlags int flags) {
-        synchronized (mLock) {
-            Preconditions.checkState(mAllPackagesCache != null,
-                    "APEX packages have not been scanned");
-            boolean matchActive = (flags & MATCH_ACTIVE_PACKAGE) != 0;
-            boolean matchFactory = (flags & MATCH_FACTORY_PACKAGE) != 0;
-            for (int i = 0, size = mAllPackagesCache.size(); i < size; i++) {
-                final Pair<ApexInfo, AndroidPackage> pair = mAllPackagesCache.get(i);
-                var apexInfo = pair.first;
-                var pkg = pair.second;
-                if (!pkg.getPackageName().equals(packageName)) {
-                    continue;
-                }
-                if ((matchActive && apexInfo.isActive)
-                        || (matchFactory && apexInfo.isFactory)) {
-                    return pair;
-                }
-            }
-            return null;
-        }
-    }
-
-    /**
-     * Retrieves information about all active APEX packages.
-     *
-     * @return list containing information about different active packages.
-     */
-    @NonNull
-    List<Pair<ApexInfo, AndroidPackage>> getActivePackages() {
-        synchronized (mLock) {
-            Preconditions.checkState(mAllPackagesCache != null,
-                    "APEX packages have not been scanned");
-            final List<Pair<ApexInfo, AndroidPackage>> activePackages = new ArrayList<>();
-            for (int i = 0; i < mAllPackagesCache.size(); i++) {
-                final var pair = mAllPackagesCache.get(i);
-                if (pair.first.isActive) {
-                    activePackages.add(pair);
-                }
-            }
-            return activePackages;
-        }
-    }
-
-    /**
-     * Retrieves information about all pre-installed APEX packages.
-     *
-     * @return list containing information about different pre-installed packages.
-     */
-    @NonNull
-    List<Pair<ApexInfo, AndroidPackage>> getFactoryPackages() {
-        synchronized (mLock) {
-            Preconditions.checkState(mAllPackagesCache != null,
-                    "APEX packages have not been scanned");
-            final List<Pair<ApexInfo, AndroidPackage>> factoryPackages = new ArrayList<>();
-            for (int i = 0; i < mAllPackagesCache.size(); i++) {
-                final var pair = mAllPackagesCache.get(i);
-                if (pair.first.isFactory) {
-                    factoryPackages.add(pair);
-                }
-            }
-            return factoryPackages;
-        }
-    }
-
-    /**
-     * Retrieves information about all inactive APEX packages.
-     *
-     * @return list containing information about different inactive packages.
-     */
-    @NonNull
-    List<Pair<ApexInfo, AndroidPackage>> getInactivePackages() {
-        synchronized (mLock) {
-            Preconditions.checkState(mAllPackagesCache != null,
-                    "APEX packages have not been scanned");
-            final List<Pair<ApexInfo, AndroidPackage>> inactivePackages = new ArrayList<>();
-            for (int i = 0; i < mAllPackagesCache.size(); i++) {
-                final var pair = mAllPackagesCache.get(i);
-                if (!pair.first.isActive) {
-                    inactivePackages.add(pair);
-                }
-            }
-            return inactivePackages;
-        }
-    }
-
-    /**
-     * Checks if {@code packageName} is an apex package.
-     *
-     * @param packageName package to check.
-     * @return {@code true} if {@code packageName} is an apex package.
-     */
-    boolean isApexPackage(String packageName) {
-        synchronized (mLock) {
-            Preconditions.checkState(mAllPackagesCache != null,
-                    "APEX packages have not been scanned");
-            for (int i = 0, size = mAllPackagesCache.size(); i < size; i++) {
-                final var pair = mAllPackagesCache.get(i);
-                if (pair.second.getPackageName().equals(packageName)) {
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
-    /**
-     * Called to update cached PackageInfo when installing rebootless APEX.
-     */
-    void notifyPackageInstalled(ApexInfo apexInfo, PackageParser2 packageParser)
-            throws PackageManagerException {
-        final int flags = PackageManager.GET_META_DATA
-                | PackageManager.GET_SIGNING_CERTIFICATES
-                | PackageManager.GET_SIGNATURES;
-        final ParsedPackage parsedPackage = packageParser.parsePackage(
-                new File(apexInfo.modulePath), flags, /* useCaches= */ false);
-        notifyPackageInstalled(apexInfo, parsedPackage.hideAsFinal());
-    }
-
-    void notifyPackageInstalled(ApexInfo apexInfo, AndroidPackage pkg) {
-        final String packageName = pkg.getPackageName();
-        synchronized (mLock) {
-            for (int i = 0, size = mAllPackagesCache.size(); i < size; i++) {
-                var pair = mAllPackagesCache.get(i);
-                var oldApexInfo = pair.first;
-                var oldApexPkg = pair.second;
-                if (oldApexInfo.isActive && oldApexPkg.getPackageName().equals(packageName)) {
-                    if (oldApexInfo.isFactory) {
-                        oldApexInfo.isActive = false;
-                        mAllPackagesCache.add(Pair.create(apexInfo, pkg));
-                    } else {
-                        mAllPackagesCache.set(i, Pair.create(apexInfo, pkg));
-                    }
-                    break;
-                }
-            }
-        }
-    }
-
-    /**
-     * Dumps various state information to the provided {@link PrintWriter} object.
-     *
-     * @param pw the {@link PrintWriter} object to send information to.
-     * @param packageName a {@link String} containing a package name, or {@code null}. If set, only
-     *                    information about that specific package will be dumped.
-     */
-    void dump(PrintWriter pw, @Nullable String packageName) {
-        final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ", 120);
-        synchronized (mLock) {
-            if (mAllPackagesCache == null) {
-                ipw.println("APEX packages have not been scanned");
-                return;
-            }
-        }
-        ipw.println("Active APEX packages:");
-        dumpPackages(getActivePackages(), packageName, ipw);
-        ipw.println("Inactive APEX packages:");
-        dumpPackages(getInactivePackages(), packageName, ipw);
-        ipw.println("Factory APEX packages:");
-        dumpPackages(getFactoryPackages(), packageName, ipw);
-    }
-
-    @GuardedBy("mLock")
-    private void notifyScanResultLocked(List<ApexManager.ScanResult> scanResults) {
-        mAllPackagesCache = new ArrayList<>();
-        final int flags = PackageManager.GET_META_DATA
-                | PackageManager.GET_SIGNING_CERTIFICATES
-                | PackageManager.GET_SIGNATURES;
-
-        HashSet<String> activePackagesSet = new HashSet<>();
-        HashSet<String> factoryPackagesSet = new HashSet<>();
-        for (ApexManager.ScanResult result : scanResults) {
-            ApexInfo ai = result.apexInfo;
-            String packageName = result.pkg.getPackageName();
-            if (!packageName.equals(result.packageName)) {
-                throw new IllegalStateException("Unmatched package name: "
-                        + result.packageName + " != " + packageName
-                        + ", path=" + ai.modulePath);
-            }
-            mAllPackagesCache.add(Pair.create(ai, result.pkg));
-            if (ai.isActive) {
-                if (!activePackagesSet.add(packageName)) {
-                    throw new IllegalStateException(
-                            "Two active packages have the same name: " + packageName);
-                }
-            }
-            if (ai.isFactory) {
-                // Don't throw when the duplicating APEX is VNDK APEX
-                if (!factoryPackagesSet.add(packageName)
-                        && !ai.moduleName.startsWith(VNDK_APEX_MODULE_NAME_PREFIX)) {
-                    throw new IllegalStateException(
-                            "Two factory packages have the same name: " + packageName);
-                }
-            }
-        }
-    }
-
-    @GuardedBy("mLock")
-    private List<ApexManager.ScanResult> scanApexPackagesInternalLocked(final ApexInfo[] allPkgs,
-            PackageParser2 packageParser, ExecutorService executorService) {
-        if (allPkgs == null || allPkgs.length == 0) {
-            notifyScanResultLocked(Collections.EMPTY_LIST);
-            return Collections.EMPTY_LIST;
-        }
-
-        ArrayMap<File, ApexInfo> parsingApexInfo = new ArrayMap<>();
-        ParallelPackageParser parallelPackageParser =
-                new ParallelPackageParser(packageParser, executorService);
-        for (ApexInfo ai : allPkgs) {
-            File apexFile = new File(ai.modulePath);
-            parallelPackageParser.submit(apexFile,
-                    ParsingPackageUtils.PARSE_COLLECT_CERTIFICATES);
-            parsingApexInfo.put(apexFile, ai);
-        }
-
-        List<ApexManager.ScanResult> results = new ArrayList<>(parsingApexInfo.size());
-        // Process results one by one
-        for (int i = 0; i < parsingApexInfo.size(); i++) {
-            ParallelPackageParser.ParseResult parseResult = parallelPackageParser.take();
-            Throwable throwable = parseResult.throwable;
-            ApexInfo ai = parsingApexInfo.get(parseResult.scanFile);
-
-            if (throwable == null) {
-                // TODO: When ENABLE_FEATURE_SCAN_APEX is finalized, remove this and the entire
-                //  calling path code
-                ScanPackageUtils.applyPolicy(parseResult.parsedPackage,
-                        PackageManagerService.SCAN_AS_SYSTEM,
-                        mPackageManager == null ? null : mPackageManager.getPlatformPackage(),
-                        false);
-                // Calling hideAsFinal to assign derived fields for the app info flags.
-                AndroidPackage finalPkg = parseResult.parsedPackage.hideAsFinal();
-                results.add(new ApexManager.ScanResult(ai, finalPkg, finalPkg.getPackageName()));
-            } else if (throwable instanceof PackageManagerException) {
-                throw new IllegalStateException("Unable to parse: " + ai.modulePath, throwable);
-            } else {
-                throw new IllegalStateException("Unexpected exception occurred while parsing "
-                        + ai.modulePath, throwable);
-            }
-        }
-
-        notifyScanResultLocked(results);
-        return results;
-    }
-
-    /**
-     * @see #dumpPackages(List, String, IndentingPrintWriter)
-     */
-    static void dumpPackageStates(List<PackageStateInternal> packageStates, boolean isActive,
-            @Nullable String packageName, IndentingPrintWriter ipw) {
-        ipw.println();
-        ipw.increaseIndent();
-        for (int i = 0, size = packageStates.size(); i < size; i++) {
-            final var packageState = packageStates.get(i);
-            var pkg = packageState.getPkg();
-            if (packageName != null && !packageName.equals(pkg.getPackageName())) {
-                continue;
-            }
-            ipw.println(pkg.getPackageName());
-            ipw.increaseIndent();
-            ipw.println("Version: " + pkg.getLongVersionCode());
-            ipw.println("Path: " + pkg.getBaseApkPath());
-            ipw.println("IsActive: " + isActive);
-            ipw.println("IsFactory: " + !packageState.isUpdatedSystemApp());
-            ipw.println("ApplicationInfo: ");
-            ipw.increaseIndent();
-            // TODO: Dump the package manually
-            AndroidPackageUtils.generateAppInfoWithoutState(pkg)
-                    .dump(new PrintWriterPrinter(ipw), "");
-            ipw.decreaseIndent();
-            ipw.decreaseIndent();
-        }
-        ipw.decreaseIndent();
-        ipw.println();
-    }
-
-    /**
-     * Dump information about the packages contained in a particular cache
-     * @param packagesCache the cache to print information about.
-     * @param packageName a {@link String} containing a package name, or {@code null}. If set,
-     *                    only information about that specific package will be dumped.
-     * @param ipw the {@link IndentingPrintWriter} object to send information to.
-     */
-    static void dumpPackages(List<Pair<ApexInfo, AndroidPackage>> packagesCache,
-            @Nullable String packageName, IndentingPrintWriter ipw) {
-        ipw.println();
-        ipw.increaseIndent();
-        for (int i = 0, size = packagesCache.size(); i < size; i++) {
-            final var pair = packagesCache.get(i);
-            var apexInfo = pair.first;
-            var pkg = pair.second;
-            if (packageName != null && !packageName.equals(pkg.getPackageName())) {
-                continue;
-            }
-            ipw.println(pkg.getPackageName());
-            ipw.increaseIndent();
-            ipw.println("Version: " + pkg.getLongVersionCode());
-            ipw.println("Path: " + pkg.getBaseApkPath());
-            ipw.println("IsActive: " + apexInfo.isActive);
-            ipw.println("IsFactory: " + apexInfo.isFactory);
-            ipw.println("ApplicationInfo: ");
-            ipw.increaseIndent();
-            // TODO: Dump the package manually
-            AndroidPackageUtils.generateAppInfoWithoutState(pkg)
-                    .dump(new PrintWriterPrinter(ipw), "");
-            ipw.decreaseIndent();
-            ipw.decreaseIndent();
-        }
-        ipw.decreaseIndent();
-        ipw.println();
-    }
-}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/pm/AppDataHelper.java b/services/core/java/com/android/server/pm/AppDataHelper.java
index b8e1e9a..f3cfa95 100644
--- a/services/core/java/com/android/server/pm/AppDataHelper.java
+++ b/services/core/java/com/android/server/pm/AppDataHelper.java
@@ -61,7 +61,7 @@
 /**
  * Prepares app data for users
  */
-final class AppDataHelper {
+public class AppDataHelper {
     private static final boolean DEBUG_APP_DATA = false;
 
     private final PackageManagerService mPm;
diff --git a/services/core/java/com/android/server/pm/AppsFilterImpl.java b/services/core/java/com/android/server/pm/AppsFilterImpl.java
index 4c21195..2e67bf2 100644
--- a/services/core/java/com/android/server/pm/AppsFilterImpl.java
+++ b/services/core/java/com/android/server/pm/AppsFilterImpl.java
@@ -22,6 +22,15 @@
 import static android.provider.DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE;
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE;
+import static com.android.internal.util.FrameworkStatsLog.PACKAGE_MANAGER_APPS_FILTER_CACHE_BUILD_REPORTED;
+import static com.android.internal.util.FrameworkStatsLog.PACKAGE_MANAGER_APPS_FILTER_CACHE_BUILD_REPORTED__EVENT_TYPE__BOOT;
+import static com.android.internal.util.FrameworkStatsLog.PACKAGE_MANAGER_APPS_FILTER_CACHE_BUILD_REPORTED__EVENT_TYPE__USER_CREATED;
+import static com.android.internal.util.FrameworkStatsLog.PACKAGE_MANAGER_APPS_FILTER_CACHE_BUILD_REPORTED__EVENT_TYPE__USER_DELETED;
+import static com.android.internal.util.FrameworkStatsLog.PACKAGE_MANAGER_APPS_FILTER_CACHE_UPDATE_REPORTED;
+import static com.android.internal.util.FrameworkStatsLog.PACKAGE_MANAGER_APPS_FILTER_CACHE_UPDATE_REPORTED__EVENT_TYPE__COMPAT_CHANGED;
+import static com.android.internal.util.FrameworkStatsLog.PACKAGE_MANAGER_APPS_FILTER_CACHE_UPDATE_REPORTED__EVENT_TYPE__PACKAGE_ADDED;
+import static com.android.internal.util.FrameworkStatsLog.PACKAGE_MANAGER_APPS_FILTER_CACHE_UPDATE_REPORTED__EVENT_TYPE__PACKAGE_DELETED;
+import static com.android.internal.util.FrameworkStatsLog.PACKAGE_MANAGER_APPS_FILTER_CACHE_UPDATE_REPORTED__EVENT_TYPE__PACKAGE_REPLACED;
 import static com.android.server.pm.AppsFilterUtils.canQueryAsInstaller;
 import static com.android.server.pm.AppsFilterUtils.canQueryViaComponents;
 import static com.android.server.pm.AppsFilterUtils.canQueryViaPackage;
@@ -36,6 +45,7 @@
 import android.content.pm.SigningDetails;
 import android.content.pm.UserInfo;
 import android.os.Handler;
+import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.Trace;
 import android.os.UserHandle;
@@ -49,6 +59,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.ArrayUtils;
+import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.FgThread;
 import com.android.server.compat.CompatChange;
 import com.android.server.om.OverlayReferenceMapper;
@@ -351,8 +362,15 @@
             if (pkg == null) {
                 return;
             }
+            final long currentTimeUs = SystemClock.currentTimeMicro();
             updateEnabledState(pkg);
             mAppsFilter.updateShouldFilterCacheForPackage(snapshot, packageName);
+            mAppsFilter.logCacheUpdated(
+                    PACKAGE_MANAGER_APPS_FILTER_CACHE_UPDATE_REPORTED__EVENT_TYPE__COMPAT_CHANGED,
+                    SystemClock.currentTimeMicro() - currentTimeUs,
+                    snapshot.getUserInfos().length,
+                    snapshot.getPackageStates().size(),
+                    pkg.getUid());
         }
 
         private void updateEnabledState(@NonNull AndroidPackage pkg) {
@@ -465,7 +483,8 @@
         mOverlayReferenceMapper.rebuildIfDeferred();
         mFeatureConfig.onSystemReady();
 
-        updateEntireShouldFilterCacheAsync(pmInternal);
+        updateEntireShouldFilterCacheAsync(pmInternal,
+                PACKAGE_MANAGER_APPS_FILTER_CACHE_BUILD_REPORTED__EVENT_TYPE__BOOT);
     }
 
     /**
@@ -473,16 +492,23 @@
      *
      * @param newPkgSetting the new setting being added
      * @param isReplace     if the package is being replaced and may need extra cleanup.
+     * @param retainImplicitGrantOnReplace {@code true} to retain implicit grant access if
+     *                                     the package is being replaced.
      */
     public void addPackage(Computer snapshot, PackageStateInternal newPkgSetting,
-            boolean isReplace) {
+            boolean isReplace, boolean retainImplicitGrantOnReplace) {
+        final long currentTimeUs = SystemClock.currentTimeMicro();
+        final int logType = isReplace
+                ? PACKAGE_MANAGER_APPS_FILTER_CACHE_UPDATE_REPORTED__EVENT_TYPE__PACKAGE_REPLACED
+                : PACKAGE_MANAGER_APPS_FILTER_CACHE_UPDATE_REPORTED__EVENT_TYPE__PACKAGE_ADDED;
         if (DEBUG_TRACING) {
             Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "filter.addPackage");
         }
         try {
             if (isReplace) {
                 // let's first remove any prior rules for this package
-                removePackage(snapshot, newPkgSetting, true /*isReplace*/);
+                removePackageInternal(snapshot, newPkgSetting,
+                        true /*isReplace*/, retainImplicitGrantOnReplace);
             }
             final ArrayMap<String, ? extends PackageStateInternal> settings =
                     snapshot.getPackageStates();
@@ -508,6 +534,8 @@
                         }
                     }
                 }
+                logCacheUpdated(logType, SystemClock.currentTimeMicro() - currentTimeUs,
+                        users.length, settings.size(), newPkgSetting.getAppId());
             } else {
                 invalidateCache("addPackage: " + newPkgSetting.getPackageName());
             }
@@ -757,18 +785,19 @@
         }
     }
 
-    private void updateEntireShouldFilterCacheAsync(PackageManagerInternal pmInternal) {
-        updateEntireShouldFilterCacheAsync(pmInternal, CACHE_REBUILD_DELAY_MIN_MS);
+    private void updateEntireShouldFilterCacheAsync(PackageManagerInternal pmInternal, int reason) {
+        updateEntireShouldFilterCacheAsync(pmInternal, CACHE_REBUILD_DELAY_MIN_MS, reason);
     }
 
     private void updateEntireShouldFilterCacheAsync(PackageManagerInternal pmInternal,
-            long delayMs) {
+            long delayMs, int reason) {
         mBackgroundHandler.postDelayed(() -> {
             if (!mCacheValid.compareAndSet(CACHE_INVALID, CACHE_VALID)) {
                 // Cache is already valid.
                 return;
             }
 
+            final long currentTimeUs = SystemClock.currentTimeMicro();
             final ArrayMap<String, AndroidPackage> packagesCache = new ArrayMap<>();
             final UserInfo[][] usersRef = new UserInfo[1][];
             final Computer snapshot = (Computer) pmInternal.snapshot();
@@ -787,11 +816,13 @@
 
             updateEntireShouldFilterCacheInner(snapshot, settings, usersRef[0], USER_ALL);
             onChanged();
+            logCacheRebuilt(reason, SystemClock.currentTimeMicro() - currentTimeUs,
+                    users.length, settings.size());
 
             if (!mCacheValid.compareAndSet(CACHE_VALID, CACHE_VALID)) {
                 Slog.i(TAG, "Cache invalidated while building, retrying.");
                 updateEntireShouldFilterCacheAsync(pmInternal,
-                        Math.min(delayMs * 2, CACHE_REBUILD_DELAY_MAX_MS));
+                        Math.min(delayMs * 2, CACHE_REBUILD_DELAY_MAX_MS), reason);
                 return;
             }
 
@@ -803,15 +834,27 @@
         if (!mCacheReady) {
             return;
         }
+        final long currentTimeUs = SystemClock.currentTimeMicro();
         updateEntireShouldFilterCache(snapshot, newUserId);
+        logCacheRebuilt(
+                PACKAGE_MANAGER_APPS_FILTER_CACHE_BUILD_REPORTED__EVENT_TYPE__USER_CREATED,
+                SystemClock.currentTimeMicro() - currentTimeUs,
+                snapshot.getUserInfos().length,
+                snapshot.getPackageStates().size());
     }
 
-    public void onUserDeleted(@UserIdInt int userId) {
+    public void onUserDeleted(Computer snapshot, @UserIdInt int userId) {
         if (!mCacheReady) {
             return;
         }
+        final long currentTimeUs = SystemClock.currentTimeMicro();
         removeShouldFilterCacheForUser(userId);
         onChanged();
+        logCacheRebuilt(
+                PACKAGE_MANAGER_APPS_FILTER_CACHE_BUILD_REPORTED__EVENT_TYPE__USER_DELETED,
+                SystemClock.currentTimeMicro() - currentTimeUs,
+                snapshot.getUserInfos().length,
+                snapshot.getPackageStates().size());
     }
 
     private void updateShouldFilterCacheForPackage(Computer snapshot,
@@ -976,13 +1019,31 @@
     }
 
     /**
-     * Equivalent to calling {@link #addPackage(Computer, PackageStateInternal, boolean)}
-     * with {@code isReplace} equal to {@code false}.
+     * Equivalent to calling {@link #addPackage(Computer, PackageStateInternal, boolean, boolean)}
+     * with {@code isReplace} and {@code retainImplicitGrantOnReplace} equal to {@code false}.
      *
-     * @see AppsFilterImpl#addPackage(Computer, PackageStateInternal, boolean)
+     * @see AppsFilterImpl#addPackage(Computer, PackageStateInternal, boolean, boolean)
      */
     public void addPackage(Computer snapshot, PackageStateInternal newPkgSetting) {
-        addPackage(snapshot, newPkgSetting, false /* isReplace */);
+        addPackage(snapshot, newPkgSetting, false /* isReplace */,
+                false /* retainImplicitGrantOnReplace */);
+    }
+
+    /**
+     * Removes a package for consideration when filtering visibility between apps.
+     *
+     * @param setting the setting of the package being removed.
+     */
+    public void removePackage(Computer snapshot, PackageStateInternal setting) {
+        final long currentTimeUs = SystemClock.currentTimeMicro();
+        removePackageInternal(snapshot, setting,
+                false /* isReplace */, false /* retainImplicitGrantOnReplace */);
+        logCacheUpdated(
+                PACKAGE_MANAGER_APPS_FILTER_CACHE_UPDATE_REPORTED__EVENT_TYPE__PACKAGE_DELETED,
+                SystemClock.currentTimeMicro() - currentTimeUs,
+                snapshot.getUserInfos().length,
+                snapshot.getPackageStates().size(),
+                setting.getAppId());
     }
 
     /**
@@ -990,33 +1051,37 @@
      *
      * @param setting   the setting of the package being removed.
      * @param isReplace if the package is being replaced.
+     * @param retainImplicitGrantOnReplace {@code true} to retain implicit grant access if
+     *                                     the package is being replaced.
      */
-    public void removePackage(Computer snapshot, PackageStateInternal setting,
-            boolean isReplace) {
+    private void removePackageInternal(Computer snapshot, PackageStateInternal setting,
+            boolean isReplace, boolean retainImplicitGrantOnReplace) {
         final ArraySet<String> additionalChangedPackages;
         final ArrayMap<String, ? extends PackageStateInternal> settings =
                 snapshot.getPackageStates();
         final UserInfo[] users = snapshot.getUserInfos();
         final Collection<SharedUserSetting> sharedUserSettings = snapshot.getAllSharedUsers();
         final int userCount = users.length;
-        synchronized (mImplicitlyQueryableLock) {
-            for (int u = 0; u < userCount; u++) {
-                final int userId = users[u].id;
-                final int removingUid = UserHandle.getUid(userId, setting.getAppId());
-                mImplicitlyQueryable.remove(removingUid);
-                for (int i = mImplicitlyQueryable.size() - 1; i >= 0; i--) {
-                    mImplicitlyQueryable.remove(mImplicitlyQueryable.keyAt(i),
-                            removingUid);
-                }
+        if (!isReplace || !retainImplicitGrantOnReplace) {
+            synchronized (mImplicitlyQueryableLock) {
+                for (int u = 0; u < userCount; u++) {
+                    final int userId = users[u].id;
+                    final int removingUid = UserHandle.getUid(userId, setting.getAppId());
+                    mImplicitlyQueryable.remove(removingUid);
+                    for (int i = mImplicitlyQueryable.size() - 1; i >= 0; i--) {
+                        mImplicitlyQueryable.remove(mImplicitlyQueryable.keyAt(i),
+                                removingUid);
+                    }
 
-                if (isReplace) {
-                    continue;
-                }
+                    if (isReplace) {
+                        continue;
+                    }
 
-                mRetainedImplicitlyQueryable.remove(removingUid);
-                for (int i = mRetainedImplicitlyQueryable.size() - 1; i >= 0; i--) {
-                    mRetainedImplicitlyQueryable.remove(
-                            mRetainedImplicitlyQueryable.keyAt(i), removingUid);
+                    mRetainedImplicitlyQueryable.remove(removingUid);
+                    for (int i = mRetainedImplicitlyQueryable.size() - 1; i >= 0; i--) {
+                        mRetainedImplicitlyQueryable.remove(
+                                mRetainedImplicitlyQueryable.keyAt(i), removingUid);
+                    }
                 }
             }
         }
@@ -1174,4 +1239,18 @@
             }
         }
     }
+
+    private void logCacheRebuilt(int eventId, long latency, int userCount, int packageCount) {
+        FrameworkStatsLog.write(PACKAGE_MANAGER_APPS_FILTER_CACHE_BUILD_REPORTED,
+                eventId, latency, userCount, packageCount, mShouldFilterCache.size());
+    }
+
+    private void logCacheUpdated(int eventId, long latency, int userCount, int packageCount,
+            int appId) {
+        if (!mCacheReady) {
+            return;
+        }
+        FrameworkStatsLog.write(PACKAGE_MANAGER_APPS_FILTER_CACHE_UPDATE_REPORTED,
+                eventId, appId, latency, userCount, packageCount, mShouldFilterCache.size());
+    }
 }
diff --git a/services/core/java/com/android/server/pm/BackgroundDexOptService.java b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
index e7412c5..d856d54 100644
--- a/services/core/java/com/android/server/pm/BackgroundDexOptService.java
+++ b/services/core/java/com/android/server/pm/BackgroundDexOptService.java
@@ -410,7 +410,7 @@
                                 job.jobFinished(params, !completed);
                             } else {
                                 // Periodic job
-                                job.jobFinished(params, true);
+                                job.jobFinished(params, false /* reschedule */);
                             }
                             markDexOptCompleted();
                         }
diff --git a/services/core/java/com/android/server/pm/BackgroundInstallControlService.java b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
new file mode 100644
index 0000000..df95f86
--- /dev/null
+++ b/services/core/java/com/android/server/pm/BackgroundInstallControlService.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.pm.IBackgroundInstallControlService;
+import android.content.pm.IPackageManager;
+import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ParceledListSlice;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.util.SparseArrayMap;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.server.SystemService;
+
+/**
+ * @hide
+ */
+public class BackgroundInstallControlService extends SystemService {
+    private static final String TAG = "BackgroundInstallControlService";
+
+    private final Context mContext;
+    private final BinderService mBinderService;
+    private final IPackageManager mIPackageManager;
+
+    // User ID -> package name -> time diff
+    // The time diff between the last foreground activity installer and
+    // the "onPackageAdded" function call.
+    private final SparseArrayMap<String, Long> mBackgroundInstalledPackages =
+            new SparseArrayMap<>();
+
+    public BackgroundInstallControlService(@NonNull Context context) {
+        this(new InjectorImpl(context));
+    }
+
+    @VisibleForTesting
+    BackgroundInstallControlService(@NonNull Injector injector) {
+        super(injector.getContext());
+        mContext = injector.getContext();
+        mIPackageManager = injector.getIPackageManager();
+        mBinderService = new BinderService(this);
+    }
+
+    private static final class BinderService extends IBackgroundInstallControlService.Stub {
+        final BackgroundInstallControlService mService;
+
+        BinderService(BackgroundInstallControlService service)  {
+            mService = service;
+        }
+
+        @Override
+        public ParceledListSlice<PackageInfo> getBackgroundInstalledPackages(
+                @PackageManager.PackageInfoFlagsBits long flags, int userId) {
+            ParceledListSlice<PackageInfo> packages;
+            try {
+                packages = mService.mIPackageManager.getInstalledPackages(flags, userId);
+            } catch (RemoteException e) {
+                throw new IllegalStateException("Package manager not available", e);
+            }
+
+            // TODO(b/244216300): to enable the test the usage by BinaryTransparencyService,
+            // we currently comment out the actual implementation.
+            // The fake implementation is just to filter out the first app of the list.
+            // for (int i = 0, size = packages.getList().size(); i < size; i++) {
+            //     String packageName = packages.getList().get(i).packageName;
+            //     if (!mBackgroundInstalledPackages.contains(userId, packageName) {
+            //         packages.getList().remove(i);
+            //     }
+            // }
+            if (packages.getList().size() > 0) {
+                packages.getList().remove(0);
+            }
+            return packages;
+        }
+    }
+
+    /**
+     * Called when the system service should publish a binder service using
+     * {@link #publishBinderService(String, IBinder).}
+     */
+    @Override
+    public void onStart() {
+        publishBinderService(Context.BACKGROUND_INSTALL_CONTROL_SERVICE, mBinderService);
+    }
+
+    /**
+     * Dependency injector for {@link #BackgroundInstallControlService)}.
+     */
+    interface Injector {
+        Context getContext();
+
+        IPackageManager getIPackageManager();
+    }
+
+    private static final class InjectorImpl implements Injector {
+        private final Context mContext;
+
+        InjectorImpl(Context context) {
+            mContext = context;
+        }
+
+        @Override
+        public Context getContext() {
+            return mContext;
+        }
+
+        @Override
+        public IPackageManager getIPackageManager() {
+            return IPackageManager.Stub.asInterface(ServiceManager.getService("package"));
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/pm/CloneProfileResolver.java b/services/core/java/com/android/server/pm/CloneProfileResolver.java
new file mode 100644
index 0000000..d3113e1
--- /dev/null
+++ b/services/core/java/com/android/server/pm/CloneProfileResolver.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm;
+
+import android.content.Intent;
+import android.content.pm.ResolveInfo;
+
+import com.android.server.pm.pkg.PackageStateInternal;
+import com.android.server.pm.resolution.ComponentResolverApi;
+import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Function;
+
+/**
+ * Cross Profile intent resolution strategy used for and to clone profile.
+ */
+public class CloneProfileResolver extends CrossProfileResolver {
+
+    public CloneProfileResolver(ComponentResolverApi componentResolver,
+            UserManagerService userManagerService) {
+        super(componentResolver, userManagerService);
+    }
+
+    /**
+     * This is resolution strategy for Clone Profile.
+     * In case of clone profile, the profile is supposed to be transparent to end user. To end user
+     * clone and owner profile should be part of same user space. Hence, the resolution strategy
+     * would resolve intent in both profile and return combined result without any filtering of the
+     * results.
+     *
+     * @param computer ComputerEngine instance that would be needed by ComponentResolverApi
+     * @param intent request
+     * @param resolvedType the MIME data type of intent request
+     * @param userId source/initiating user
+     * @param targetUserId target user id
+     * @param flags of intent request
+     * @param pkgName the application package name this Intent is limited to
+     * @param matchingFilters {@link CrossProfileIntentFilter}s configured for source user,
+     *                                                        targeting the targetUserId
+     * @param hasNonNegativePriorityResult if source have any non-negative(active and valid)
+     *                                     resolveInfo in their profile.
+     * @param pkgSettingFunction function to find PackageStateInternal for given package
+     * @return list of {@link CrossProfileDomainInfo}
+     */
+    @Override
+    public List<CrossProfileDomainInfo> resolveIntent(Computer computer, Intent intent,
+            String resolvedType, int userId, int targetUserId, long flags,
+            String pkgName, List<CrossProfileIntentFilter> matchingFilters,
+            boolean hasNonNegativePriorityResult,
+            Function<String, PackageStateInternal> pkgSettingFunction) {
+        List<ResolveInfo> resolveInfos = mComponentResolver.queryActivities(computer,
+                intent, resolvedType, flags, targetUserId);
+        List<CrossProfileDomainInfo> crossProfileDomainInfos = new ArrayList<>();
+        if (resolveInfos != null) {
+
+            for (int index = 0; index < resolveInfos.size(); index++) {
+                crossProfileDomainInfos.add(new CrossProfileDomainInfo(resolveInfos.get(index),
+                        DomainVerificationManagerInternal.APPROVAL_LEVEL_NONE,
+                        targetUserId));
+            }
+        }
+        return filterIfNotSystemUser(crossProfileDomainInfos, userId);
+    }
+
+    /**
+     * As clone and owner profile are going to be part of the same userspace, we need no filtering
+     * out of any clone profile's result
+     * @param intent request
+     * @param crossProfileDomainInfos resolved in target user
+     * @param flags for intent resolution
+     * @param sourceUserId source user
+     * @param targetUserId target user
+     * @param highestApprovalLevel highest level of domain approval
+     * @return list of CrossProfileDomainInfo
+     */
+    @Override
+    public List<CrossProfileDomainInfo> filterResolveInfoWithDomainPreferredActivity(Intent intent,
+            List<CrossProfileDomainInfo> crossProfileDomainInfos, long flags, int sourceUserId,
+            int targetUserId, int highestApprovalLevel) {
+        // no filtering for clone profile
+        return crossProfileDomainInfos;
+    }
+}
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index b9967f9..b285136 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -63,7 +63,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
-import android.apex.ApexInfo;
 import android.app.ActivityManager;
 import android.content.ComponentName;
 import android.content.Context;
@@ -112,6 +111,7 @@
 import android.util.LongSparseLongArray;
 import android.util.MathUtils;
 import android.util.Pair;
+import android.util.PrintWriterPrinter;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.Xml;
@@ -396,7 +396,6 @@
     private final UserManagerService mUserManager;
     private final PermissionManagerServiceInternal mPermissionManager;
     private final ApexManager mApexManager;
-    private final ApexPackageInfo mApexPackageInfo;
     private final PackageManagerServiceInjector mInjector;
     private final ComponentResolverApi mComponentResolver;
     private final InstantAppResolverConnection mInstantAppResolverConnection;
@@ -452,7 +451,6 @@
         mContext = args.service.mContext;
         mInjector = args.service.mInjector;
         mApexManager = args.service.mApexManager;
-        mApexPackageInfo = args.service.mApexPackageInfo;
         mInstantAppResolverConnection = args.service.mInstantAppResolverConnection;
         mDefaultAppProvider = args.service.getDefaultAppProvider();
         mDomainVerificationManager = args.service.mDomainVerificationManager;
@@ -462,7 +460,7 @@
         mBackgroundDexOptService = args.service.mBackgroundDexOptService;
         mExternalSourcesPolicy = args.service.mExternalSourcesPolicy;
         mCrossProfileIntentResolverEngine = new CrossProfileIntentResolverEngine(
-                mUserManager, mDomainVerificationManager, mDefaultAppProvider);
+                mUserManager, mDomainVerificationManager, mDefaultAppProvider, mContext);
 
         // Used to reference PMS attributes that are primitives and which are not
         // updated under control of the PMS lock.
@@ -968,10 +966,8 @@
         if (p != null) {
             PackageStateInternal ps = mSettings.getPackage(packageName);
             if (ps == null) return null;
-            if (ApexPackageInfo.ENABLE_FEATURE_SCAN_APEX) {
-                if (!matchApex && p.isApex()) {
-                    return null;
-                }
+            if (!matchApex && p.isApex()) {
+                return null;
             }
             if (filterSharedLibPackage(ps, filterCallingUid, userId, flags)) {
                 return null;
@@ -987,24 +983,6 @@
             }
             return ai;
         }
-        if (!ApexPackageInfo.ENABLE_FEATURE_SCAN_APEX) {
-            if (matchApex) {
-                // For APKs, PackageInfo.applicationInfo is not exactly the same as ApplicationInfo
-                // returned from getApplicationInfo, but for APEX packages difference shouldn't be
-                // very big.
-                // TODO(b/155328545): generate proper application info for APEXes as well.
-                int apexFlags = ApexManager.MATCH_ACTIVE_PACKAGE;
-                if ((flags & PackageManager.MATCH_SYSTEM_ONLY) != 0) {
-                    apexFlags = ApexManager.MATCH_FACTORY_PACKAGE;
-                }
-                final var pair = mApexPackageInfo.getPackageInfo(packageName, apexFlags);
-                if (pair == null) {
-                    return null;
-                }
-                return PackageInfoUtils.generateApplicationInfo(pair.second, flags,
-                        PackageUserStateInternal.DEFAULT, userId, null);
-            }
-        }
         if ("android".equals(packageName) || "system".equals(packageName)) {
             return androidApplication();
         }
@@ -1553,22 +1531,10 @@
         final boolean matchApex = (flags & MATCH_APEX) != 0;
         if (matchFactoryOnly) {
             // Instant app filtering for APEX modules is ignored
-            if (!ApexPackageInfo.ENABLE_FEATURE_SCAN_APEX) {
-                if (matchApex) {
-                    final var pair = mApexPackageInfo.getPackageInfo(packageName,
-                            ApexManager.MATCH_FACTORY_PACKAGE);
-                    if (pair == null) {
-                        return null;
-                    }
-                    return PackageInfoUtils.generate(pair.second, pair.first, flags, null, userId);
-                }
-            }
             final PackageStateInternal ps = mSettings.getDisabledSystemPkg(packageName);
             if (ps != null) {
-                if (ApexPackageInfo.ENABLE_FEATURE_SCAN_APEX) {
-                    if (!matchApex && ps.getPkg() != null && ps.getPkg().isApex()) {
-                        return null;
-                    }
+                if (!matchApex && ps.getPkg() != null && ps.getPkg().isApex()) {
+                    return null;
                 }
                 if (filterSharedLibPackage(ps, filterCallingUid, userId, flags)) {
                     return null;
@@ -1589,10 +1555,8 @@
         }
         if (p != null) {
             final PackageStateInternal ps = getPackageStateInternal(p.getPackageName());
-            if (ApexPackageInfo.ENABLE_FEATURE_SCAN_APEX) {
-                if (!matchApex && p.isApex()) {
-                    return null;
-                }
+            if (!matchApex && p.isApex()) {
+                return null;
             }
             if (filterSharedLibPackage(ps, filterCallingUid, userId, flags)) {
                 return null;
@@ -1614,16 +1578,6 @@
             }
             return generatePackageInfo(ps, flags, userId);
         }
-        if (!ApexPackageInfo.ENABLE_FEATURE_SCAN_APEX) {
-            if (matchApex) {
-                final var pair = mApexPackageInfo.getPackageInfo(packageName,
-                        ApexManager.MATCH_ACTIVE_PACKAGE);
-                if (pair == null) {
-                    return null;
-                }
-                return PackageInfoUtils.generate(pair.second, pair.first, flags, null, userId);
-            }
-        }
         return null;
     }
 
@@ -1692,10 +1646,8 @@
                         ps = psDisabled;
                     }
                 }
-                if (ApexPackageInfo.ENABLE_FEATURE_SCAN_APEX) {
-                    if (!listApex && ps.getPkg() != null && ps.getPkg().isApex()) {
-                        continue;
-                    }
+                if (!listApex && ps.getPkg() != null && ps.getPkg().isApex()) {
+                    continue;
                 }
                 if (filterSharedLibPackage(ps, callingUid, userId, flags)) {
                     continue;
@@ -1722,10 +1674,8 @@
                         ps = psDisabled;
                     }
                 }
-                if (ApexPackageInfo.ENABLE_FEATURE_SCAN_APEX) {
-                    if (!listApex && p.isApex()) {
-                        continue;
-                    }
+                if (!listApex && p.isApex()) {
+                    continue;
                 }
                 if (filterSharedLibPackage(ps, callingUid, userId, flags)) {
                     continue;
@@ -1739,22 +1689,6 @@
                 }
             }
         }
-        if (!ApexPackageInfo.ENABLE_FEATURE_SCAN_APEX) {
-            if (listApex) {
-                List<Pair<ApexInfo, AndroidPackage>> pairs;
-                if (listFactory) {
-                    pairs = mApexPackageInfo.getFactoryPackages();
-                } else {
-                    pairs = mApexPackageInfo.getActivePackages();
-                }
-
-                for (int index = 0; index < pairs.size(); index++) {
-                    var pair = pairs.get(index);
-                    list.add(PackageInfoUtils.generate(pair.second, pair.first, flags, null,
-                            userId));
-                }
-            }
-        }
         return new ParceledListSlice<>(list);
     }
 
@@ -3155,24 +3089,56 @@
     }
 
     private void dumpApex(PrintWriter pw, String packageName) {
-        if (ApexPackageInfo.ENABLE_FEATURE_SCAN_APEX) {
-            final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ", 120);
-            List<PackageStateInternal> activePackages = new ArrayList<>();
-            List<PackageStateInternal> inactivePackages = new ArrayList<>();
-            List<PackageStateInternal> factoryActivePackages = new ArrayList<>();
-            List<PackageStateInternal> factoryInactivePackages = new ArrayList<>();
-            generateApexPackageInfo(activePackages, inactivePackages, factoryActivePackages,
-                    factoryInactivePackages);
-            ipw.println("Active APEX packages:");
-            ApexPackageInfo.dumpPackageStates(activePackages, true, packageName, ipw);
-            ipw.println("Inactive APEX packages:");
-            ApexPackageInfo.dumpPackageStates(inactivePackages, false, packageName, ipw);
-            ipw.println("Factory APEX packages:");
-            ApexPackageInfo.dumpPackageStates(factoryActivePackages, true, packageName, ipw);
-            ApexPackageInfo.dumpPackageStates(factoryInactivePackages, false, packageName, ipw);
-        } else {
-            mApexPackageInfo.dump(pw, packageName);
+        final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ", 120);
+        List<PackageStateInternal> activePackages = new ArrayList<>();
+        List<PackageStateInternal> inactivePackages = new ArrayList<>();
+        List<PackageStateInternal> factoryActivePackages = new ArrayList<>();
+        List<PackageStateInternal> factoryInactivePackages = new ArrayList<>();
+        generateApexPackageInfo(activePackages, inactivePackages, factoryActivePackages,
+                factoryInactivePackages);
+        ipw.println("Active APEX packages:");
+        dumpApexPackageStates(activePackages, true, packageName, ipw);
+        ipw.println("Inactive APEX packages:");
+        dumpApexPackageStates(inactivePackages, false, packageName, ipw);
+        ipw.println("Factory APEX packages:");
+        dumpApexPackageStates(factoryActivePackages, true, packageName, ipw);
+        dumpApexPackageStates(factoryInactivePackages, false, packageName, ipw);
+    }
+
+
+    /**
+     * Dump information about the packages contained in a particular cache
+     * @param packageStates the states to print information about.
+     * @param packageName a {@link String} containing a package name, or {@code null}. If set,
+     *                    only information about that specific package will be dumped.
+     * @param ipw the {@link IndentingPrintWriter} object to send information to.
+     */
+    private static void dumpApexPackageStates(List<PackageStateInternal> packageStates,
+            boolean isActive, @Nullable String packageName, IndentingPrintWriter ipw) {
+        ipw.println();
+        ipw.increaseIndent();
+        for (int i = 0, size = packageStates.size(); i < size; i++) {
+            final var packageState = packageStates.get(i);
+            var pkg = packageState.getPkg();
+            if (packageName != null && !packageName.equals(pkg.getPackageName())) {
+                continue;
+            }
+            ipw.println(pkg.getPackageName());
+            ipw.increaseIndent();
+            ipw.println("Version: " + pkg.getLongVersionCode());
+            ipw.println("Path: " + pkg.getBaseApkPath());
+            ipw.println("IsActive: " + isActive);
+            ipw.println("IsFactory: " + !packageState.isUpdatedSystemApp());
+            ipw.println("ApplicationInfo: ");
+            ipw.increaseIndent();
+            // TODO: Dump the package manually
+            AndroidPackageUtils.generateAppInfoWithoutState(pkg)
+                    .dump(new PrintWriterPrinter(ipw), "");
+            ipw.decreaseIndent();
+            ipw.decreaseIndent();
         }
+        ipw.decreaseIndent();
+        ipw.println();
     }
 
     // The body of findPreferredActivity.
@@ -3551,12 +3517,8 @@
 
     @Override
     public boolean isApexPackage(String packageName) {
-        if (!ApexPackageInfo.ENABLE_FEATURE_SCAN_APEX) {
-            return mApexPackageInfo.isApexPackage(packageName);
-        } else {
-            final AndroidPackage pkg = mPackages.get(packageName);
-            return pkg != null && pkg.isApex();
-        }
+        final AndroidPackage pkg = mPackages.get(packageName);
+        return pkg != null && pkg.isApex();
     }
 
     @Override
@@ -4563,10 +4525,8 @@
                     effectiveFlags |= PackageManager.MATCH_ANY_USER;
                 }
                 if (ps.getPkg() != null) {
-                    if (ApexPackageInfo.ENABLE_FEATURE_SCAN_APEX) {
-                        if (!listApex && ps.getPkg().isApex()) {
-                            continue;
-                        }
+                    if (!listApex && ps.getPkg().isApex()) {
+                        continue;
                     }
                     if (filterSharedLibPackage(ps, callingUid, userId, flags)) {
                         continue;
@@ -4596,10 +4556,8 @@
                 if (pkg == null) {
                     continue;
                 }
-                if (ApexPackageInfo.ENABLE_FEATURE_SCAN_APEX) {
-                    if (!listApex && pkg.isApex()) {
-                        continue;
-                    }
+                if (!listApex && pkg.isApex()) {
+                    continue;
                 }
                 if (filterSharedLibPackage(packageState, Binder.getCallingUid(), userId, flags)) {
                     continue;
diff --git a/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java b/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java
index 0cd698a..798217f 100644
--- a/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java
+++ b/services/core/java/com/android/server/pm/CrossProfileIntentFilter.java
@@ -46,6 +46,9 @@
     private static final String ATTR_FILTER = "filter";
     private static final String ATTR_ACCESS_CONTROL = "accessControl";
 
+    //flag to decide if intent needs to be resolved cross profile if pkgName is already defined
+    public static final int FLAG_IS_PACKAGE_FOR_FILTER = 0x00000008;
+
     private static final String TAG = "CrossProfileIntentFilter";
 
     /**
diff --git a/services/core/java/com/android/server/pm/CrossProfileIntentResolver.java b/services/core/java/com/android/server/pm/CrossProfileIntentResolver.java
index 9ea16d3..2581878 100644
--- a/services/core/java/com/android/server/pm/CrossProfileIntentResolver.java
+++ b/services/core/java/com/android/server/pm/CrossProfileIntentResolver.java
@@ -16,6 +16,8 @@
 
 package com.android.server.pm;
 
+import static com.android.server.pm.CrossProfileIntentFilter.FLAG_IS_PACKAGE_FOR_FILTER;
+
 import android.annotation.NonNull;
 import android.content.IntentFilter;
 
@@ -37,7 +39,7 @@
 
     @Override
     protected boolean isPackageForFilter(String packageName, CrossProfileIntentFilter filter) {
-        return false;
+        return (FLAG_IS_PACKAGE_FOR_FILTER & filter.mFlags) != 0;
     }
 
     @Override
diff --git a/services/core/java/com/android/server/pm/CrossProfileIntentResolverEngine.java b/services/core/java/com/android/server/pm/CrossProfileIntentResolverEngine.java
index 7752fdf..5ae4cab 100644
--- a/services/core/java/com/android/server/pm/CrossProfileIntentResolverEngine.java
+++ b/services/core/java/com/android/server/pm/CrossProfileIntentResolverEngine.java
@@ -25,12 +25,14 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
 import android.content.pm.UserInfo;
 import android.os.Process;
 import android.text.TextUtils;
+import android.util.FeatureFlagUtils;
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
@@ -54,13 +56,15 @@
     private final UserManagerService mUserManager;
     private final DomainVerificationManagerInternal mDomainVerificationManager;
     private final DefaultAppProvider mDefaultAppProvider;
+    private final Context mContext;
 
     public CrossProfileIntentResolverEngine(UserManagerService userManager,
             DomainVerificationManagerInternal domainVerificationManager,
-            DefaultAppProvider defaultAppProvider) {
+            DefaultAppProvider defaultAppProvider, Context context) {
         mUserManager = userManager;
         mDomainVerificationManager = domainVerificationManager;
         mDefaultAppProvider = defaultAppProvider;
+        mContext = context;
     }
 
     /**
@@ -196,6 +200,21 @@
     @SuppressWarnings("unused")
     private CrossProfileResolver chooseCrossProfileResolver(@NonNull Computer computer,
             UserInfo sourceUserInfo, UserInfo targetUserInfo) {
+        //todo change isCloneProfile to user properties b/241532322
+        /**
+         * If source or target user is clone profile, using {@link CloneProfileResolver}
+         * We would allow CloneProfileResolver only if flag
+         * SETTINGS_ALLOW_INTENT_REDIRECTION_FOR_CLONE_PROFILE is enabled
+         */
+        if (sourceUserInfo.isCloneProfile() || targetUserInfo.isCloneProfile()) {
+            if (FeatureFlagUtils.isEnabled(mContext,
+                    FeatureFlagUtils.SETTINGS_ALLOW_INTENT_REDIRECTION_FOR_CLONE_PROFILE)) {
+                return new CloneProfileResolver(computer.getComponentResolver(),
+                        mUserManager);
+            } else {
+                return null;
+            }
+        }
         return new DefaultCrossProfileResolver(computer.getComponentResolver(),
                 mUserManager, mDomainVerificationManager);
     }
@@ -313,8 +332,6 @@
         }
 
         if (pkgName == null && intent.hasWebURI()) {
-            // If instant apps are not allowed and there is result only from current or cross
-            // profile return it
             if (!addInstant && ((candidates.size() <= 1 && crossProfileCandidates.isEmpty())
                     || (candidates.isEmpty() && !crossProfileCandidates.isEmpty()))) {
                 candidates.addAll(resolveInfoFromCrossProfileDomainInfo(crossProfileCandidates));
diff --git a/services/core/java/com/android/server/pm/InitAppsHelper.java b/services/core/java/com/android/server/pm/InitAppsHelper.java
index 6f59096..12b5ab8 100644
--- a/services/core/java/com/android/server/pm/InitAppsHelper.java
+++ b/services/core/java/com/android/server/pm/InitAppsHelper.java
@@ -72,7 +72,6 @@
     private final int mSystemScanFlags;
     private final InstallPackageHelper mInstallPackageHelper;
     private final ApexManager mApexManager;
-    private final ApexPackageInfo mApexPackageInfo;
     private final ExecutorService mExecutorService;
     /* Tracks how long system scan took */
     private long mSystemScanTime;
@@ -96,13 +95,11 @@
     private final List<String> mStubSystemApps = new ArrayList<>();
 
     // TODO(b/198166813): remove PMS dependency
-    InitAppsHelper(PackageManagerService pm,
-            ApexManager apexManager, ApexPackageInfo apexPackageInfo,
+    InitAppsHelper(PackageManagerService pm, ApexManager apexManager,
             InstallPackageHelper installPackageHelper,
             List<ScanPartition> systemPartitions) {
         mPm = pm;
         mApexManager = apexManager;
-        mApexPackageInfo = apexPackageInfo;
         mInstallPackageHelper = installPackageHelper;
         mSystemPartitions = systemPartitions;
         mDirsToScanAsSystem = getSystemScanPartitions();
@@ -165,16 +162,8 @@
         Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanApexPackages");
 
         try {
-            final List<ApexManager.ScanResult> apexScanResults;
-            if (ApexPackageInfo.ENABLE_FEATURE_SCAN_APEX) {
-                apexScanResults = mInstallPackageHelper.scanApexPackages(
-                        mApexManager.getAllApexInfos(), mSystemParseFlags, mSystemScanFlags,
-                        packageParser, mExecutorService);
-            } else {
-                apexScanResults = mApexPackageInfo.scanApexPackages(
-                        mApexManager.getAllApexInfos(), packageParser, mExecutorService);
-            }
-            return apexScanResults;
+            return mInstallPackageHelper.scanApexPackages(mApexManager.getAllApexInfos(),
+                    mSystemParseFlags, mSystemScanFlags, packageParser, mExecutorService);
         } finally {
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
         }
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 9d007c9..7ea0c04 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -463,7 +463,8 @@
 
             final Computer snapshot = mPm.snapshotComputer();
             mPm.mComponentResolver.addAllComponents(pkg, chatty, mPm.mSetupWizardPackage, snapshot);
-            mPm.mAppsFilter.addPackage(snapshot, pkgSetting, isReplace);
+            mPm.mAppsFilter.addPackage(snapshot, pkgSetting, isReplace,
+                    (scanFlags & SCAN_DONT_KILL_APP) != 0 /* retainImplicitGrantOnReplace */);
             mPm.addAllPackageProperties(pkg);
 
             if (oldPkgSetting == null || oldPkgSetting.getPkg() == null) {
diff --git a/services/core/java/com/android/server/pm/InstallingSession.java b/services/core/java/com/android/server/pm/InstallingSession.java
index 16b3a81..88469f5 100644
--- a/services/core/java/com/android/server/pm/InstallingSession.java
+++ b/services/core/java/com/android/server/pm/InstallingSession.java
@@ -572,19 +572,15 @@
             }
             try (PackageParser2 packageParser = mPm.mInjector.getScanningPackageParser()) {
                 ApexInfo apexInfo = mPm.mApexManager.installPackage(apexes[0]);
-                if (ApexPackageInfo.ENABLE_FEATURE_SCAN_APEX) {
-                    // APEX has been handled successfully by apexd. Let's continue the install flow
-                    // so it will be scanned and registered with the system.
-                    // TODO(b/225756739): Improve atomicity of rebootless APEX install.
-                    // The newly installed APEX will not be reverted even if
-                    // processApkInstallRequests() fails. Need a way to keep info stored in apexd
-                    // and PMS in sync in the face of install failures.
-                    request.setApexInfo(apexInfo);
-                    mPm.mHandler.post(() -> processApkInstallRequests(true, requests));
-                    return;
-                } else {
-                    mPm.mApexPackageInfo.notifyPackageInstalled(apexInfo, packageParser);
-                }
+                // APEX has been handled successfully by apexd. Let's continue the install flow
+                // so it will be scanned and registered with the system.
+                // TODO(b/225756739): Improve atomicity of rebootless APEX install.
+                // The newly installed APEX will not be reverted even if
+                // processApkInstallRequests() fails. Need a way to keep info stored in apexd
+                // and PMS in sync in the face of install failures.
+                request.setApexInfo(apexInfo);
+                mPm.mHandler.post(() -> processApkInstallRequests(true, requests));
+                return;
             }
         } catch (PackageManagerException e) {
             request.setError("APEX installation failed", e);
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 5df73a6..72ec510 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -2763,10 +2763,11 @@
                     "Missing existing base package");
         }
 
-        // Default to require only if existing base apk has fs-verity.
+        // Default to require only if existing base apk has fs-verity signature.
         mVerityFoundForApks = PackageManagerServiceUtils.isApkVerityEnabled()
                 && params.mode == SessionParams.MODE_INHERIT_EXISTING
-                && VerityUtils.hasFsverity(pkgInfo.applicationInfo.getBaseCodePath());
+                && (new File(VerityUtils.getFsveritySignatureFilePath(
+                        pkgInfo.applicationInfo.getBaseCodePath()))).exists();
 
         final List<File> removedFiles = getRemovedFilesLocked();
         final List<String> removeSplitList = new ArrayList<>();
@@ -3326,7 +3327,7 @@
                     "Failure to obtain package info.");
         }
         final List<String> filePaths = packageLite.getAllApkPaths();
-        final String appLabel = mPreapprovalDetails.getLabel();
+        final CharSequence appLabel = mPreapprovalDetails.getLabel();
         final ULocale appLocale = mPreapprovalDetails.getLocale();
         final ApplicationInfo appInfo = packageInfo.applicationInfo;
         boolean appLabelMatched = false;
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index dfbe68a..8089af3 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -107,6 +107,7 @@
 import android.content.pm.SuspendDialogInfo;
 import android.content.pm.TestUtilityService;
 import android.content.pm.UserInfo;
+import android.content.pm.UserPackage;
 import android.content.pm.VerifierDeviceIdentity;
 import android.content.pm.VersionedPackage;
 import android.content.pm.overlay.OverlayPaths;
@@ -690,7 +691,6 @@
     private final ModuleInfoProvider mModuleInfoProvider;
 
     final ApexManager mApexManager;
-    final ApexPackageInfo mApexPackageInfo;
 
     final PackageManagerServiceInjector mInjector;
 
@@ -1147,7 +1147,7 @@
         var done = SystemClock.currentTimeMicro();
 
         if (mSnapshotStatistics != null) {
-            mSnapshotStatistics.rebuild(now, done, hits);
+            mSnapshotStatistics.rebuild(now, done, hits, newSnapshot.getPackageStates().size());
         }
         return newSnapshot;
     }
@@ -1641,7 +1641,6 @@
         mSharedLibraries = injector.getSharedLibrariesImpl();
 
         mApexManager = testParams.apexManager;
-        mApexPackageInfo = new ApexPackageInfo(this);
         mArtManagerService = testParams.artManagerService;
         mAvailableFeatures = testParams.availableFeatures;
         mBackgroundDexOptService = testParams.backgroundDexOptService;
@@ -1841,7 +1840,6 @@
         mProtectedPackages = new ProtectedPackages(mContext);
 
         mApexManager = injector.getApexManager();
-        mApexPackageInfo = new ApexPackageInfo(this);
         mAppsFilter = mInjector.getAppsFilter();
 
         mInstantAppRegistry = new InstantAppRegistry(mContext, mPermissionManager,
@@ -1979,8 +1977,8 @@
                         + ver.fingerprint + " to " + PackagePartitions.FINGERPRINT);
             }
 
-            mInitAppsHelper = new InitAppsHelper(this, mApexManager, mApexPackageInfo,
-                mInstallPackageHelper, mInjector.getSystemPartitions());
+            mInitAppsHelper = new InitAppsHelper(this, mApexManager, mInstallPackageHelper,
+                    mInjector.getSystemPartitions());
 
             // when upgrading from pre-M, promote system app permissions from install to runtime
             mPromoteSystemApps =
@@ -2989,6 +2987,7 @@
     @Override
     public void notifyPackageRemoved(String packageName, int uid) {
         mPackageObserverHelper.notifyRemoved(packageName, uid);
+        UserPackage.removeFromCache(UserHandle.getUserId(uid), packageName);
     }
 
     void sendPackageAddedForUser(@NonNull Computer snapshot, String packageName,
@@ -4220,7 +4219,7 @@
             mSettings.removeUserLPw(userId);
             mPendingBroadcasts.remove(userId);
             mDeletePackageHelper.removeUnusedPackagesLPw(userManager, userId);
-            mAppsFilter.onUserDeleted(userId);
+            mAppsFilter.onUserDeleted(snapshotComputer(), userId);
         }
         mInstantAppRegistry.onUserRemoved(userId);
     }
@@ -5064,8 +5063,11 @@
                     "getUnsuspendablePackagesForUser");
             final int callingUid = Binder.getCallingUid();
             if (UserHandle.getUserId(callingUid) != userId) {
-                throw new SecurityException("Calling uid " + callingUid
-                        + " cannot query getUnsuspendablePackagesForUser for user " + userId);
+                mContext.enforceCallingOrSelfPermission(
+                        Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+                        "Calling uid " + callingUid
+                                + " cannot query getUnsuspendablePackagesForUser for user "
+                                + userId);
             }
             return mSuspendPackageHelper.getUnsuspendablePackagesForUser(snapshotComputer(),
                     packageNames, userId, callingUid);
diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index bbc4fde..7e93673 100644
--- a/services/core/java/com/android/server/pm/RemovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java
@@ -308,7 +308,7 @@
                 mPm.mSettings.getKeySetManagerService().removeAppKeySetDataLPw(packageName);
                 final Computer snapshot = mPm.snapshotComputer();
                 mPm.mAppsFilter.removePackage(snapshot,
-                        snapshot.getPackageStateInternal(packageName), false /* isReplace */);
+                        snapshot.getPackageStateInternal(packageName));
                 removedAppId = mPm.mSettings.removePackageLPw(packageName);
                 if (outInfo != null) {
                     outInfo.mRemovedAppId = removedAppId;
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 12c9d4b..45c0d6e 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -455,19 +455,24 @@
     // The user's preferred activities associated with particular intent
     // filters.
     @Watched
-    private final WatchedSparseArray<PreferredIntentResolver>
-            mPreferredActivities = new WatchedSparseArray<>();
+    private final WatchedSparseArray<PreferredIntentResolver> mPreferredActivities;
+    private final SnapshotCache<WatchedSparseArray<PreferredIntentResolver>>
+            mPreferredActivitiesSnapshot;
 
     // The persistent preferred activities of the user's profile/device owner
     // associated with particular intent filters.
     @Watched
     private final WatchedSparseArray<PersistentPreferredIntentResolver>
-            mPersistentPreferredActivities = new WatchedSparseArray<>();
+            mPersistentPreferredActivities;
+    private final SnapshotCache<WatchedSparseArray<PersistentPreferredIntentResolver>>
+            mPersistentPreferredActivitiesSnapshot;
+
 
     // For every user, it is used to find to which other users the intent can be forwarded.
     @Watched
-    private final WatchedSparseArray<CrossProfileIntentResolver>
-            mCrossProfileIntentResolvers = new WatchedSparseArray<>();
+    private final WatchedSparseArray<CrossProfileIntentResolver> mCrossProfileIntentResolvers;
+    private final SnapshotCache<WatchedSparseArray<CrossProfileIntentResolver>>
+            mCrossProfileIntentResolversSnapshot;
 
     @Watched
     final WatchedArrayMap<String, SharedUserSetting> mSharedUsers = new WatchedArrayMap<>();
@@ -476,11 +481,12 @@
 
     // For reading/writing settings file.
     @Watched
-    private final WatchedArrayList<Signature> mPastSignatures =
-            new WatchedArrayList<Signature>();
+    private final WatchedArrayList<Signature> mPastSignatures;
+    private final SnapshotCache<WatchedArrayList<Signature>> mPastSignaturesSnapshot;
+
     @Watched
-    private final WatchedArrayMap<Long, Integer> mKeySetRefs =
-            new WatchedArrayMap<Long, Integer>();
+    private final WatchedArrayMap<Long, Integer> mKeySetRefs;
+    private final SnapshotCache<WatchedArrayMap<Long, Integer>> mKeySetRefsSnapshot;
 
     // Packages that have been renamed since they were first installed.
     // Keys are the new names of the packages, values are the original
@@ -511,7 +517,8 @@
      * scanning to make it less confusing.
      */
     @Watched
-    private final WatchedArrayList<PackageSetting> mPendingPackages = new WatchedArrayList<>();
+    private final WatchedArrayList<PackageSetting> mPendingPackages;
+    private final SnapshotCache<WatchedArrayList<PackageSetting>> mPendingPackagesSnapshot;
 
     private final File mSystemDir;
 
@@ -583,6 +590,26 @@
         mInstallerPackagesSnapshot =
                 new SnapshotCache.Auto<>(mInstallerPackages, mInstallerPackages,
                                          "Settings.mInstallerPackages");
+        mPreferredActivities = new WatchedSparseArray<>();
+        mPreferredActivitiesSnapshot = new SnapshotCache.Auto<>(mPreferredActivities,
+                mPreferredActivities, "Settings.mPreferredActivities");
+        mPersistentPreferredActivities = new WatchedSparseArray<>();
+        mPersistentPreferredActivitiesSnapshot = new SnapshotCache.Auto<>(
+                mPersistentPreferredActivities, mPersistentPreferredActivities,
+                "Settings.mPersistentPreferredActivities");
+        mCrossProfileIntentResolvers = new WatchedSparseArray<>();
+        mCrossProfileIntentResolversSnapshot = new SnapshotCache.Auto<>(
+                mCrossProfileIntentResolvers, mCrossProfileIntentResolvers,
+                "Settings.mCrossProfileIntentResolvers");
+        mPastSignatures = new WatchedArrayList<>();
+        mPastSignaturesSnapshot = new SnapshotCache.Auto<>(mPastSignatures, mPastSignatures,
+                "Settings.mPastSignatures");
+        mKeySetRefs = new WatchedArrayMap<>();
+        mKeySetRefsSnapshot = new SnapshotCache.Auto<>(mKeySetRefs, mKeySetRefs,
+                "Settings.mKeySetRefs");
+        mPendingPackages = new WatchedArrayList<>();
+        mPendingPackagesSnapshot = new SnapshotCache.Auto<>(mPendingPackages, mPendingPackages,
+                "Settings.mPendingPackages");
         mKeySetManagerService = new KeySetManagerService(mPackages);
 
         // Test-only handler working on background thread.
@@ -623,6 +650,26 @@
         mInstallerPackagesSnapshot =
                 new SnapshotCache.Auto<>(mInstallerPackages, mInstallerPackages,
                                          "Settings.mInstallerPackages");
+        mPreferredActivities = new WatchedSparseArray<>();
+        mPreferredActivitiesSnapshot = new SnapshotCache.Auto<>(mPreferredActivities,
+                mPreferredActivities, "Settings.mPreferredActivities");
+        mPersistentPreferredActivities = new WatchedSparseArray<>();
+        mPersistentPreferredActivitiesSnapshot = new SnapshotCache.Auto<>(
+                mPersistentPreferredActivities, mPersistentPreferredActivities,
+                "Settings.mPersistentPreferredActivities");
+        mCrossProfileIntentResolvers = new WatchedSparseArray<>();
+        mCrossProfileIntentResolversSnapshot = new SnapshotCache.Auto<>(
+                mCrossProfileIntentResolvers, mCrossProfileIntentResolvers,
+                "Settings.mCrossProfileIntentResolvers");
+        mPastSignatures = new WatchedArrayList<>();
+        mPastSignaturesSnapshot = new SnapshotCache.Auto<>(mPastSignatures, mPastSignatures,
+                "Settings.mPastSignatures");
+        mKeySetRefs = new WatchedArrayMap<>();
+        mKeySetRefsSnapshot = new SnapshotCache.Auto<>(mKeySetRefs, mKeySetRefs,
+                "Settings.mKeySetRefs");
+        mPendingPackages = new WatchedArrayList<>();
+        mPendingPackagesSnapshot = new SnapshotCache.Auto<>(mPendingPackages, mPendingPackages,
+                "Settings.mPendingPackages");
         mKeySetManagerService = new KeySetManagerService(mPackages);
 
         mHandler = handler;
@@ -699,24 +746,27 @@
         mBlockUninstallPackages.snapshot(r.mBlockUninstallPackages);
         mVersion.putAll(r.mVersion);
         mVerifierDeviceIdentity = r.mVerifierDeviceIdentity;
-        WatchedSparseArray.snapshot(
-                mPreferredActivities, r.mPreferredActivities);
-        WatchedSparseArray.snapshot(
-                mPersistentPreferredActivities, r.mPersistentPreferredActivities);
-        WatchedSparseArray.snapshot(
-                mCrossProfileIntentResolvers, r.mCrossProfileIntentResolvers);
+        mPreferredActivities = r.mPreferredActivitiesSnapshot.snapshot();
+        mPreferredActivitiesSnapshot = new SnapshotCache.Sealed<>();
+        mPersistentPreferredActivities = r.mPersistentPreferredActivitiesSnapshot.snapshot();
+        mPersistentPreferredActivitiesSnapshot = new SnapshotCache.Sealed<>();
+        mCrossProfileIntentResolvers = r.mCrossProfileIntentResolversSnapshot.snapshot();
+        mCrossProfileIntentResolversSnapshot = new SnapshotCache.Sealed<>();
+
         mSharedUsers.snapshot(r.mSharedUsers);
         mAppIds = r.mAppIds.snapshot();
-        WatchedArrayList.snapshot(
-                mPastSignatures, r.mPastSignatures);
-        WatchedArrayMap.snapshot(
-                mKeySetRefs, r.mKeySetRefs);
+
+        mPastSignatures = r.mPastSignaturesSnapshot.snapshot();
+        mPastSignaturesSnapshot = new SnapshotCache.Sealed<>();
+        mKeySetRefs = r.mKeySetRefsSnapshot.snapshot();
+        mKeySetRefsSnapshot = new SnapshotCache.Sealed<>();
+
         mRenamedPackages.snapshot(r.mRenamedPackages);
         mNextAppLinkGeneration.snapshot(r.mNextAppLinkGeneration);
         mDefaultBrowserApp.snapshot(r.mDefaultBrowserApp);
         // mReadMessages
-        WatchedArrayList.snapshot(
-                mPendingPackages, r.mPendingPackages);
+        mPendingPackages = r.mPendingPackagesSnapshot.snapshot();
+        mPendingPackagesSnapshot = new SnapshotCache.Sealed<>();
         mSystemDir = null;
         // mKeySetManagerService;
         mPermissions = r.mPermissions;
diff --git a/services/core/java/com/android/server/pm/ShortcutLauncher.java b/services/core/java/com/android/server/pm/ShortcutLauncher.java
index 0d99075..00f7dc4 100644
--- a/services/core/java/com/android/server/pm/ShortcutLauncher.java
+++ b/services/core/java/com/android/server/pm/ShortcutLauncher.java
@@ -20,6 +20,7 @@
 import android.annotation.UserIdInt;
 import android.content.pm.PackageInfo;
 import android.content.pm.ShortcutInfo;
+import android.content.pm.UserPackage;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.AtomicFile;
@@ -30,7 +31,6 @@
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
 import com.android.server.pm.ShortcutService.DumpFilter;
-import com.android.server.pm.ShortcutUser.PackageWithUser;
 
 import libcore.io.IoUtils;
 
@@ -70,7 +70,7 @@
     /**
      * Package name -> IDs.
      */
-    final private ArrayMap<PackageWithUser, ArraySet<String>> mPinnedShortcuts = new ArrayMap<>();
+    private final ArrayMap<UserPackage, ArraySet<String>> mPinnedShortcuts = new ArrayMap<>();
 
     private ShortcutLauncher(@NonNull ShortcutUser shortcutUser,
             @UserIdInt int ownerUserId, @NonNull String packageName,
@@ -101,12 +101,12 @@
      * Called when the new package can't receive the backup, due to signature or version mismatch.
      */
     private void onRestoreBlocked() {
-        final ArrayList<PackageWithUser> pinnedPackages =
+        final ArrayList<UserPackage> pinnedPackages =
                 new ArrayList<>(mPinnedShortcuts.keySet());
         mPinnedShortcuts.clear();
         for (int i = pinnedPackages.size() - 1; i >= 0; i--) {
-            final PackageWithUser pu = pinnedPackages.get(i);
-            final ShortcutPackage p = mShortcutUser.getPackageShortcutsIfExists(pu.packageName);
+            final UserPackage up = pinnedPackages.get(i);
+            final ShortcutPackage p = mShortcutUser.getPackageShortcutsIfExists(up.packageName);
             if (p != null) {
                 p.refreshPinnedFlags();
             }
@@ -135,13 +135,13 @@
             return; // No need to instantiate.
         }
 
-        final PackageWithUser pu = PackageWithUser.of(packageUserId, packageName);
+        final UserPackage up = UserPackage.of(packageUserId, packageName);
 
         final int idSize = ids.size();
         if (idSize == 0) {
-            mPinnedShortcuts.remove(pu);
+            mPinnedShortcuts.remove(up);
         } else {
-            final ArraySet<String> prevSet = mPinnedShortcuts.get(pu);
+            final ArraySet<String> prevSet = mPinnedShortcuts.get(up);
 
             // Actually pin shortcuts.
             // This logic here is to make sure a launcher cannot pin a shortcut that is not dynamic
@@ -165,7 +165,7 @@
                     newSet.add(id);
                 }
             }
-            mPinnedShortcuts.put(pu, newSet);
+            mPinnedShortcuts.put(up, newSet);
         }
         packageShortcuts.refreshPinnedFlags();
     }
@@ -176,7 +176,7 @@
     @Nullable
     public ArraySet<String> getPinnedShortcutIds(@NonNull String packageName,
             @UserIdInt int packageUserId) {
-        return mPinnedShortcuts.get(PackageWithUser.of(packageUserId, packageName));
+        return mPinnedShortcuts.get(UserPackage.of(packageUserId, packageName));
     }
 
     /**
@@ -207,7 +207,7 @@
     }
 
     boolean cleanUpPackage(String packageName, @UserIdInt int packageUserId) {
-        return mPinnedShortcuts.remove(PackageWithUser.of(packageUserId, packageName)) != null;
+        return mPinnedShortcuts.remove(UserPackage.of(packageUserId, packageName)) != null;
     }
 
     public void ensurePackageInfo() {
@@ -241,15 +241,15 @@
         getPackageInfo().saveToXml(mShortcutUser.mService, out, forBackup);
 
         for (int i = 0; i < size; i++) {
-            final PackageWithUser pu = mPinnedShortcuts.keyAt(i);
+            final UserPackage up = mPinnedShortcuts.keyAt(i);
 
-            if (forBackup && (pu.userId != getOwnerUserId())) {
+            if (forBackup && (up.userId != getOwnerUserId())) {
                 continue; // Target package on a different user, skip. (i.e. work profile)
             }
 
             out.startTag(null, TAG_PACKAGE);
-            ShortcutService.writeAttr(out, ATTR_PACKAGE_NAME, pu.packageName);
-            ShortcutService.writeAttr(out, ATTR_PACKAGE_USER_ID, pu.userId);
+            ShortcutService.writeAttr(out, ATTR_PACKAGE_NAME, up.packageName);
+            ShortcutService.writeAttr(out, ATTR_PACKAGE_USER_ID, up.userId);
 
             final ArraySet<String> ids = mPinnedShortcuts.valueAt(i);
             final int idSize = ids.size();
@@ -345,7 +345,7 @@
                                 ATTR_PACKAGE_USER_ID, ownerUserId);
                         ids = new ArraySet<>();
                         ret.mPinnedShortcuts.put(
-                                PackageWithUser.of(packageUserId, packageName), ids);
+                                UserPackage.of(packageUserId, packageName), ids);
                         continue;
                     }
                 }
@@ -386,14 +386,14 @@
         for (int i = 0; i < size; i++) {
             pw.println();
 
-            final PackageWithUser pu = mPinnedShortcuts.keyAt(i);
+            final UserPackage up = mPinnedShortcuts.keyAt(i);
 
             pw.print(prefix);
             pw.print("  ");
             pw.print("Package: ");
-            pw.print(pu.packageName);
+            pw.print(up.packageName);
             pw.print("  User: ");
-            pw.println(pu.userId);
+            pw.println(up.userId);
 
             final ArraySet<String> ids = mPinnedShortcuts.valueAt(i);
             final int idSize = ids.size();
@@ -418,7 +418,7 @@
 
     @VisibleForTesting
     ArraySet<String> getAllPinnedShortcutsForTest(String packageName, int packageUserId) {
-        return new ArraySet<>(mPinnedShortcuts.get(PackageWithUser.of(packageUserId, packageName)));
+        return new ArraySet<>(mPinnedShortcuts.get(UserPackage.of(packageUserId, packageName)));
     }
 
     @Override
diff --git a/services/core/java/com/android/server/pm/ShortcutService.java b/services/core/java/com/android/server/pm/ShortcutService.java
index b6f09ff..83720f1 100644
--- a/services/core/java/com/android/server/pm/ShortcutService.java
+++ b/services/core/java/com/android/server/pm/ShortcutService.java
@@ -57,6 +57,7 @@
 import android.content.pm.ShortcutManager;
 import android.content.pm.ShortcutServiceInternal;
 import android.content.pm.ShortcutServiceInternal.ShortcutChangeListener;
+import android.content.pm.UserPackage;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
 import android.graphics.Bitmap;
@@ -118,7 +119,6 @@
 import com.android.modules.utils.TypedXmlSerializer;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
-import com.android.server.pm.ShortcutUser.PackageWithUser;
 import com.android.server.uri.UriGrantsManagerInternal;
 
 import libcore.io.IoUtils;
@@ -3774,7 +3774,7 @@
 
         final long start = getStatStartTime();
         try {
-            final ArrayList<PackageWithUser> gonePackages = new ArrayList<>();
+            final ArrayList<UserPackage> gonePackages = new ArrayList<>();
 
             synchronized (mLock) {
                 final ShortcutUser user = getUserShortcutsLocked(ownerUserId);
@@ -3789,13 +3789,14 @@
                             Slog.d(TAG, "Uninstalled: " + spi.getPackageName()
                                     + " user " + spi.getPackageUserId());
                         }
-                        gonePackages.add(PackageWithUser.of(spi));
+                        gonePackages.add(
+                                UserPackage.of(spi.getPackageUserId(), spi.getPackageName()));
                     }
                 });
                 if (gonePackages.size() > 0) {
                     for (int i = gonePackages.size() - 1; i >= 0; i--) {
-                        final PackageWithUser pu = gonePackages.get(i);
-                        cleanUpPackageLocked(pu.packageName, ownerUserId, pu.userId,
+                        final UserPackage up = gonePackages.get(i);
+                        cleanUpPackageLocked(up.packageName, ownerUserId, up.userId,
                                 /* appStillExists = */ false);
                     }
                 }
@@ -5274,7 +5275,7 @@
             final ShortcutUser user = mUsers.get(userId);
             if (user == null) return null;
 
-            return user.getAllLaunchersForTest().get(PackageWithUser.of(userId, packageName));
+            return user.getAllLaunchersForTest().get(UserPackage.of(userId, packageName));
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/ShortcutUser.java b/services/core/java/com/android/server/pm/ShortcutUser.java
index 20bbf46..94eb6bb 100644
--- a/services/core/java/com/android/server/pm/ShortcutUser.java
+++ b/services/core/java/com/android/server/pm/ShortcutUser.java
@@ -21,6 +21,7 @@
 import android.app.appsearch.AppSearchManager;
 import android.app.appsearch.AppSearchSession;
 import android.content.pm.ShortcutManager;
+import android.content.pm.UserPackage;
 import android.metrics.LogMaker;
 import android.os.Binder;
 import android.os.FileUtils;
@@ -52,7 +53,6 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.Objects;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
@@ -82,44 +82,6 @@
     private static final String KEY_LAUNCHERS = "launchers";
     private static final String KEY_PACKAGES = "packages";
 
-    static final class PackageWithUser {
-        final int userId;
-        final String packageName;
-
-        private PackageWithUser(int userId, String packageName) {
-            this.userId = userId;
-            this.packageName = Objects.requireNonNull(packageName);
-        }
-
-        public static PackageWithUser of(int userId, String packageName) {
-            return new PackageWithUser(userId, packageName);
-        }
-
-        public static PackageWithUser of(ShortcutPackageItem spi) {
-            return new PackageWithUser(spi.getPackageUserId(), spi.getPackageName());
-        }
-
-        @Override
-        public int hashCode() {
-            return packageName.hashCode() ^ userId;
-        }
-
-        @Override
-        public boolean equals(Object obj) {
-            if (!(obj instanceof PackageWithUser)) {
-                return false;
-            }
-            final PackageWithUser that = (PackageWithUser) obj;
-
-            return userId == that.userId && packageName.equals(that.packageName);
-        }
-
-        @Override
-        public String toString() {
-            return String.format("[Package: %d, %s]", userId, packageName);
-        }
-    }
-
     final ShortcutService mService;
     final AppSearchManager mAppSearchManager;
     final Executor mExecutor;
@@ -129,7 +91,7 @@
 
     private final ArrayMap<String, ShortcutPackage> mPackages = new ArrayMap<>();
 
-    private final ArrayMap<PackageWithUser, ShortcutLauncher> mLaunchers = new ArrayMap<>();
+    private final ArrayMap<UserPackage, ShortcutLauncher> mLaunchers = new ArrayMap<>();
 
     /** In-memory-cached default launcher. */
     private String mCachedLauncher;
@@ -204,20 +166,20 @@
     // We don't expose this directly to non-test code because only ShortcutUser should add to/
     // remove from it.
     @VisibleForTesting
-    ArrayMap<PackageWithUser, ShortcutLauncher> getAllLaunchersForTest() {
+    ArrayMap<UserPackage, ShortcutLauncher> getAllLaunchersForTest() {
         return mLaunchers;
     }
 
     private void addLauncher(ShortcutLauncher launcher) {
         launcher.replaceUser(this);
-        mLaunchers.put(PackageWithUser.of(launcher.getPackageUserId(),
+        mLaunchers.put(UserPackage.of(launcher.getPackageUserId(),
                 launcher.getPackageName()), launcher);
     }
 
     @Nullable
     public ShortcutLauncher removeLauncher(
             @UserIdInt int packageUserId, @NonNull String packageName) {
-        return mLaunchers.remove(PackageWithUser.of(packageUserId, packageName));
+        return mLaunchers.remove(UserPackage.of(packageUserId, packageName));
     }
 
     @Nullable
@@ -242,7 +204,7 @@
     @NonNull
     public ShortcutLauncher getLauncherShortcuts(@NonNull String packageName,
             @UserIdInt int launcherUserId) {
-        final PackageWithUser key = PackageWithUser.of(launcherUserId, packageName);
+        final UserPackage key = UserPackage.of(launcherUserId, packageName);
         ShortcutLauncher ret = mLaunchers.get(key);
         if (ret == null) {
             ret = new ShortcutLauncher(this, mUserId, packageName, launcherUserId);
diff --git a/services/core/java/com/android/server/pm/SnapshotStatistics.java b/services/core/java/com/android/server/pm/SnapshotStatistics.java
index 2cfc894..e04a1e5 100644
--- a/services/core/java/com/android/server/pm/SnapshotStatistics.java
+++ b/services/core/java/com/android/server/pm/SnapshotStatistics.java
@@ -24,11 +24,13 @@
 import android.text.TextUtils;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.EventLogTags;
 
 import java.io.PrintWriter;
 import java.util.Arrays;
 import java.util.Locale;
+import java.util.concurrent.TimeUnit;
 
 /**
  * This class records statistics about PackageManagerService snapshots.  It maintains two sets of
@@ -59,9 +61,9 @@
     public static final int SNAPSHOT_TICK_INTERVAL_MS = 60 * 1000;
 
     /**
-     * The number of ticks for long statistics.  This is one week.
+     * The interval of the snapshot statistics logging.
      */
-    public static final int SNAPSHOT_LONG_TICKS = 7 * 24 * 60;
+    private static final long SNAPSHOT_LOG_INTERVAL_US = TimeUnit.DAYS.toMicros(1);
 
     /**
      * The number snapshot event logs that can be generated in a single logging interval.
@@ -93,6 +95,28 @@
     public static final int SNAPSHOT_SHORT_LIFETIME = 5;
 
     /**
+     *  Buckets to represent a range of the rebuild latency for the histogram of
+     *  snapshot rebuild latency.
+     */
+    private static final int REBUILD_LATENCY_BUCKET_LESS_THAN_1_MILLIS = 1;
+    private static final int REBUILD_LATENCY_BUCKET_LESS_THAN_2_MILLIS = 2;
+    private static final int REBUILD_LATENCY_BUCKET_LESS_THAN_5_MILLIS = 5;
+    private static final int REBUILD_LATENCY_BUCKET_LESS_THAN_10_MILLIS = 10;
+    private static final int REBUILD_LATENCY_BUCKET_LESS_THAN_20_MILLIS = 20;
+    private static final int REBUILD_LATENCY_BUCKET_LESS_THAN_50_MILLIS = 50;
+    private static final int REBUILD_LATENCY_BUCKET_LESS_THAN_100_MILLIS = 100;
+
+    /**
+     *  Buckets to represent a range of the reuse count for the histogram of
+     *  snapshot reuse counts.
+     */
+    private static final int REUSE_COUNT_BUCKET_LESS_THAN_1 = 1;
+    private static final int REUSE_COUNT_BUCKET_LESS_THAN_10 = 10;
+    private static final int REUSE_COUNT_BUCKET_LESS_THAN_100 = 100;
+    private static final int REUSE_COUNT_BUCKET_LESS_THAN_1000 = 1000;
+    private static final int REUSE_COUNT_BUCKET_LESS_THAN_10000 = 10000;
+
+    /**
      * The lock to control access to this object.
      */
     private final Object mLock = new Object();
@@ -113,11 +137,6 @@
     private int mEventsReported = 0;
 
     /**
-     * The tick counter.  At the default tick interval, this wraps every 4000 years or so.
-     */
-    private int mTicks = 0;
-
-    /**
      * The handler used for the periodic ticks.
      */
     private Handler mHandler = null;
@@ -139,8 +158,6 @@
 
         // The number of bins
         private int mCount;
-        // The mapping of low integers to bins
-        private int[] mBinMap;
         // The maximum mapped value.  Values at or above this are mapped to the
         // top bin.
         private int mMaxBin;
@@ -158,16 +175,6 @@
             mCount = mUserKey.length + 1;
             // The maximum value is one more than the last one in the map.
             mMaxBin = mUserKey[mUserKey.length - 1] + 1;
-            mBinMap = new int[mMaxBin + 1];
-
-            int j = 0;
-            for (int i = 0; i < mUserKey.length; i++) {
-                while (j <= mUserKey[i]) {
-                    mBinMap[j] = i;
-                    j++;
-                }
-            }
-            mBinMap[mMaxBin] = mUserKey.length;
         }
 
         /**
@@ -175,9 +182,14 @@
          */
         public int getBin(int x) {
             if (x >= 0 && x < mMaxBin) {
-                return mBinMap[x];
+                for (int i = 0; i < mUserKey.length; i++) {
+                    if (x <= mUserKey[i]) {
+                        return i;
+                    }
+                }
+                return 0; // should not happen
             } else if (x >= mMaxBin) {
-                return mBinMap[mMaxBin];
+                return mUserKey.length;
             } else {
                 // x is negative.  The bin will not be used.
                 return 0;
@@ -263,6 +275,11 @@
         public int mMaxBuildTimeUs = 0;
 
         /**
+         * The maximum used count since the last log.
+         */
+        public int mMaxUsedCount = 0;
+
+        /**
          * Record the rebuild.  The parameters are the length of time it took to build the
          * latest snapshot, and the number of times the _previous_ snapshot was used.  A
          * negative value for used signals an invalid value, which is the case the first
@@ -279,7 +296,6 @@
             }
 
             mTotalTimeUs += duration;
-            boolean reportIt = false;
 
             if (big) {
                 mBigBuilds++;
@@ -290,6 +306,9 @@
             if (mMaxBuildTimeUs < duration) {
                 mMaxBuildTimeUs = duration;
             }
+            if (mMaxUsedCount < used) {
+                mMaxUsedCount = used;
+            }
         }
 
         private Stats(long now) {
@@ -313,6 +332,7 @@
             mShortLived = orig.mShortLived;
             mTotalTimeUs = orig.mTotalTimeUs;
             mMaxBuildTimeUs = orig.mMaxBuildTimeUs;
+            mMaxUsedCount = orig.mMaxUsedCount;
         }
 
         /**
@@ -443,18 +463,19 @@
         }
 
         /**
-         * Report the object via an event.  Presumably the record indicates an anomalous
-         * incident.
+         * Report the snapshot statistics to FrameworkStatsLog.
          */
-        private void report() {
-            EventLogTags.writePmSnapshotStats(
-                    mTotalBuilds, mTotalUsed, mBigBuilds, mShortLived,
-                    mMaxBuildTimeUs / US_IN_MS, mTotalTimeUs / US_IN_MS);
+        private void logSnapshotStatistics(int packageCount) {
+            final long avgLatencyUs = (mTotalBuilds == 0 ? 0 : mTotalTimeUs / mTotalBuilds);
+            final int avgUsedCount = (mTotalBuilds == 0 ? 0 : mTotalUsed / mTotalBuilds);
+            FrameworkStatsLog.write(
+                    FrameworkStatsLog.PACKAGE_MANAGER_SNAPSHOT_REPORTED, mTimes, mUsed,
+                    mMaxBuildTimeUs, mMaxUsedCount, avgLatencyUs, avgUsedCount, packageCount);
         }
     }
 
     /**
-     * Long statistics.  These roll over approximately every week.
+     * Long statistics.  These roll over approximately one day.
      */
     private Stats[] mLong;
 
@@ -464,10 +485,14 @@
     private Stats[] mShort;
 
     /**
-     * The time of the last build.  This can be used to compute the length of time a
-     * snapshot existed before being replaced.
+     * The time of last logging to the FrameworkStatsLog.
      */
-    private long mLastBuildTime = 0;
+    private long mLastLogTimeUs;
+
+    /**
+     * The number of packages on the device.
+     */
+    private int mPackageCount;
 
     /**
      * Create a snapshot object.  Initialize the bin levels.  The last bin catches
@@ -475,8 +500,20 @@
      */
     public SnapshotStatistics() {
         // Create the bin thresholds.  The time bins are in units of us.
-        mTimeBins = new BinMap(new int[] { 1, 2, 5, 10, 20, 50, 100 });
-        mUseBins = new BinMap(new int[] { 1, 2, 5, 10, 20, 50, 100 });
+        mTimeBins = new BinMap(new int[] {
+                REBUILD_LATENCY_BUCKET_LESS_THAN_1_MILLIS,
+                REBUILD_LATENCY_BUCKET_LESS_THAN_2_MILLIS,
+                REBUILD_LATENCY_BUCKET_LESS_THAN_5_MILLIS,
+                REBUILD_LATENCY_BUCKET_LESS_THAN_10_MILLIS,
+                REBUILD_LATENCY_BUCKET_LESS_THAN_20_MILLIS,
+                REBUILD_LATENCY_BUCKET_LESS_THAN_50_MILLIS,
+                REBUILD_LATENCY_BUCKET_LESS_THAN_100_MILLIS });
+        mUseBins = new BinMap(new int[] {
+                REUSE_COUNT_BUCKET_LESS_THAN_1,
+                REUSE_COUNT_BUCKET_LESS_THAN_10,
+                REUSE_COUNT_BUCKET_LESS_THAN_100,
+                REUSE_COUNT_BUCKET_LESS_THAN_1000,
+                REUSE_COUNT_BUCKET_LESS_THAN_10000 });
 
         // Create the raw statistics
         final long now = SystemClock.currentTimeMicro();
@@ -484,6 +521,7 @@
         mLong[0] = new Stats(now);
         mShort = new Stats[10];
         mShort[0] = new Stats(now);
+        mLastLogTimeUs = now;
 
         // Create the message handler for ticks and start the ticker.
         mHandler = new Handler(Looper.getMainLooper()) {
@@ -516,13 +554,14 @@
      * @param now The time at which the snapshot rebuild began, in ns.
      * @param done The time at which the snapshot rebuild completed, in ns.
      * @param hits The number of times the previous snapshot was used.
+     * @param packageCount The number of packages on the device.
      */
-    public final void rebuild(long now, long done, int hits) {
+    public final void rebuild(long now, long done, int hits, int packageCount) {
         // The duration has a span of about 2000s
         final int duration = (int) (done - now);
         boolean reportEvent = false;
         synchronized (mLock) {
-            mLastBuildTime = now;
+            mPackageCount = packageCount;
 
             final int timeBin = mTimeBins.getBin(duration / 1000);
             final int useBin = mUseBins.getBin(hits);
@@ -570,10 +609,12 @@
     private void tick() {
         synchronized (mLock) {
             long now = SystemClock.currentTimeMicro();
-            mTicks++;
-            if (mTicks % SNAPSHOT_LONG_TICKS == 0) {
+            if (now - mLastLogTimeUs > SNAPSHOT_LOG_INTERVAL_US) {
                 shift(mLong, now);
+                mLastLogTimeUs = now;
+                mLong[mLong.length - 1].logSnapshotStatistics(mPackageCount);
             }
+
             shift(mShort, now);
             mEventsReported = 0;
         }
diff --git a/services/core/java/com/android/server/pm/StorageEventHelper.java b/services/core/java/com/android/server/pm/StorageEventHelper.java
index aeb11b7..fbfc84a 100644
--- a/services/core/java/com/android/server/pm/StorageEventHelper.java
+++ b/services/core/java/com/android/server/pm/StorageEventHelper.java
@@ -196,8 +196,11 @@
                     appDataHelper.reconcileAppsDataLI(volumeUuid, user.id, flags,
                             true /* migrateAppData */);
                 }
-            } catch (IllegalStateException e) {
-                // Device was probably ejected, and we'll process that event momentarily
+            } catch (RuntimeException e) {
+                // The volume was probably already unmounted.  We'll probably process the unmount
+                // event momentarily.  TODO(b/256909937): ignoring errors from prepareUserStorage()
+                // is very dangerous.  Instead, we should fix the race condition that allows this
+                // code to run on an unmounted volume in the first place.
                 Slog.w(TAG, "Failed to prepare storage: " + e);
             }
         }
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 2bfee8c..f85b11d 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -19,6 +19,7 @@
 import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.os.UserManager.DISALLOW_USER_SWITCH;
+import static android.os.UserManager.SYSTEM_USER_MODE_EMULATION_PROPERTY;
 
 import android.Manifest;
 import android.accounts.Account;
@@ -52,6 +53,7 @@
 import android.content.pm.ShortcutServiceInternal;
 import android.content.pm.UserInfo;
 import android.content.pm.UserInfo.UserInfoFlag;
+import android.content.pm.UserPackage;
 import android.content.pm.UserProperties;
 import android.content.pm.parsing.FrameworkParsingPackageUtils;
 import android.content.res.Configuration;
@@ -104,7 +106,6 @@
 import android.util.TimeUtils;
 import android.util.TypedValue;
 import android.util.Xml;
-import android.view.Display;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -314,7 +315,7 @@
     @VisibleForTesting
     static class UserData {
         // Basic user information and properties
-        UserInfo info;
+        @NonNull UserInfo info;
         // Account name used when there is a strong association between a user and an account
         String account;
         // Account information for seeding into a newly created user. This could also be
@@ -625,14 +626,7 @@
     @GuardedBy("mUserStates")
     private final WatchedUserStates mUserStates = new WatchedUserStates();
 
-    /**
-     * Set on on devices that support background users (key) running on secondary displays (value).
-     */
-    // TODO(b/244644281): move such logic to a different class (like UserDisplayAssigner)
-    @Nullable
-    @GuardedBy("mUsersOnSecondaryDisplays")
-    private final SparseIntArray mUsersOnSecondaryDisplays;
-    private final boolean mUsersOnSecondaryDisplaysEnabled;
+    private final UserVisibilityMediator mUserVisibilityMediator;
 
     private static UserManagerService sInstance;
 
@@ -708,8 +702,7 @@
     @VisibleForTesting
     UserManagerService(Context context) {
         this(context, /* pm= */ null, /* userDataPreparer= */ null,
-                /* packagesLock= */ new Object(), context.getCacheDir(), /* users= */ null,
-                /* usersOnSecondaryDisplays= */ null);
+                /* packagesLock= */ new Object(), context.getCacheDir(), /* users= */ null);
     }
 
     /**
@@ -720,13 +713,13 @@
     UserManagerService(Context context, PackageManagerService pm, UserDataPreparer userDataPreparer,
             Object packagesLock) {
         this(context, pm, userDataPreparer, packagesLock, Environment.getDataDirectory(),
-                /* users= */ null, /* usersOnSecondaryDisplays= */ null);
+                /* users= */ null);
     }
 
     @VisibleForTesting
     UserManagerService(Context context, PackageManagerService pm,
             UserDataPreparer userDataPreparer, Object packagesLock, File dataDir,
-            SparseArray<UserData> users, @Nullable SparseIntArray usersOnSecondaryDisplays) {
+            SparseArray<UserData> users) {
         mContext = context;
         mPm = pm;
         mPackagesLock = packagesLock;
@@ -756,14 +749,7 @@
         mUserStates.put(UserHandle.USER_SYSTEM, UserState.STATE_BOOTING);
         mUser0Allocations = DBG_ALLOCATION ? new AtomicInteger() : null;
         emulateSystemUserModeIfNeeded();
-        mUsersOnSecondaryDisplaysEnabled = UserManager.isUsersOnSecondaryDisplaysEnabled();
-        if (mUsersOnSecondaryDisplaysEnabled) {
-            mUsersOnSecondaryDisplays = usersOnSecondaryDisplays == null
-                    ? new SparseIntArray() // default behavior
-                    : usersOnSecondaryDisplays; // passed by unit test
-        } else {
-            mUsersOnSecondaryDisplays = null;
-        }
+        mUserVisibilityMediator = new UserVisibilityMediator(this);
     }
 
     void systemReady() {
@@ -1652,7 +1638,8 @@
         return isProfileUnchecked(userId);
     }
 
-    private boolean isProfileUnchecked(@UserIdInt int userId) {
+    // TODO(b/244644281): make it private once UserVisibilityMediator don't use it anymore
+    boolean isProfileUnchecked(@UserIdInt int userId) {
         synchronized (mUsersLock) {
             UserInfo userInfo = getUserInfoLU(userId);
             return userInfo != null && userInfo.isProfile();
@@ -1729,11 +1716,6 @@
         return userId == getCurrentUserId();
     }
 
-    @VisibleForTesting
-    boolean isUsersOnSecondaryDisplaysEnabled() {
-        return mUsersOnSecondaryDisplaysEnabled;
-    }
-
     @Override
     public boolean isUserVisible(@UserIdInt int userId) {
         int callingUserId = UserHandle.getCallingUserId();
@@ -1744,69 +1726,7 @@
                     + ") is visible");
         }
 
-        return isUserVisibleUnchecked(userId);
-    }
-
-    @VisibleForTesting
-    boolean isUserVisibleUnchecked(@UserIdInt int userId) {
-        // First check current foreground user and their profiles (on main display)
-        if (isCurrentUserOrRunningProfileOfCurrentUser(userId)) {
-            return true;
-        }
-
-        // Device doesn't support multiple users on multiple displays, so only users checked above
-        // can be visible
-        if (!mUsersOnSecondaryDisplaysEnabled) {
-            return false;
-        }
-
-        synchronized (mUsersOnSecondaryDisplays) {
-            return mUsersOnSecondaryDisplays.indexOfKey(userId) >= 0;
-        }
-    }
-
-    @VisibleForTesting
-    int getDisplayAssignedToUser(@UserIdInt int userId) {
-        if (isCurrentUserOrRunningProfileOfCurrentUser(userId)) {
-            return Display.DEFAULT_DISPLAY;
-        }
-
-        if (!mUsersOnSecondaryDisplaysEnabled) {
-            return Display.INVALID_DISPLAY;
-        }
-
-        synchronized (mUsersOnSecondaryDisplays) {
-            return mUsersOnSecondaryDisplays.get(userId, Display.INVALID_DISPLAY);
-        }
-    }
-
-    @VisibleForTesting
-    int getUserAssignedToDisplay(int displayId) {
-        if (displayId == Display.DEFAULT_DISPLAY || !mUsersOnSecondaryDisplaysEnabled) {
-            return getCurrentUserId();
-        }
-
-        synchronized (mUsersOnSecondaryDisplays) {
-            for (int i = 0; i < mUsersOnSecondaryDisplays.size(); i++) {
-                if (mUsersOnSecondaryDisplays.valueAt(i) != displayId) {
-                    continue;
-                }
-                int userId = mUsersOnSecondaryDisplays.keyAt(i);
-                if (!isProfileUnchecked(userId)) {
-                    return userId;
-                } else if (DBG_MUMD) {
-                    Slogf.d(LOG_TAG, "getUserAssignedToDisplay(%d): skipping user %d because it's "
-                            + "a profile", displayId, userId);
-                }
-            }
-        }
-
-        int currentUserId = getCurrentUserId();
-        if (DBG_MUMD) {
-            Slogf.d(LOG_TAG, "getUserAssignedToDisplay(%d): no user assigned to display, returning "
-                    + "current user (%d) instead", displayId, currentUserId);
-        }
-        return currentUserId;
+        return mUserVisibilityMediator.isUserVisible(userId);
     }
 
     /**
@@ -1850,54 +1770,9 @@
         return false;
     }
 
-    // TODO(b/239982558): try to merge with isUserVisibleUnchecked() (once both are unit tested)
-    /**
-     * See {@link UserManagerInternal#isUserVisible(int, int)}.
-     */
+    // Called by UserManagerServiceShellCommand
     boolean isUserVisibleOnDisplay(@UserIdInt int userId, int displayId) {
-        if (displayId == Display.INVALID_DISPLAY) {
-            return false;
-        }
-        if (!mUsersOnSecondaryDisplaysEnabled) {
-            return isCurrentUserOrRunningProfileOfCurrentUser(userId);
-        }
-
-        // TODO(b/244644281): temporary workaround to let WM use this API without breaking current
-        // behavior - return true for current user / profile for any display (other than those
-        // explicitly assigned to another users), otherwise they wouldn't be able to launch
-        // activities on other non-passenger displays, like cluster, display, or virtual displays).
-        // In the long-term, it should rely just on mUsersOnSecondaryDisplays, which
-        // would be updated by DisplayManagerService when displays are created / initialized.
-        if (isCurrentUserOrRunningProfileOfCurrentUser(userId)) {
-            synchronized (mUsersOnSecondaryDisplays) {
-                boolean assignedToUser = false;
-                boolean assignedToAnotherUser = false;
-                for (int i = 0; i < mUsersOnSecondaryDisplays.size(); i++) {
-                    if (mUsersOnSecondaryDisplays.valueAt(i) == displayId) {
-                        if (mUsersOnSecondaryDisplays.keyAt(i) == userId) {
-                            assignedToUser = true;
-                            break;
-                        } else {
-                            assignedToAnotherUser = true;
-                            // Cannot break because it could be assigned to a profile of the user
-                            // (and we better not assume that the iteration will check for the
-                            // parent user before its profiles)
-                        }
-                    }
-                }
-                if (DBG_MUMD) {
-                    Slogf.d(LOG_TAG, "isUserVisibleOnDisplay(%d, %d): assignedToUser=%b, "
-                            + "assignedToAnotherUser=%b, mUsersOnSecondaryDisplays=%s",
-                            userId, displayId, assignedToUser, assignedToAnotherUser,
-                            mUsersOnSecondaryDisplays);
-                }
-                return assignedToUser || !assignedToAnotherUser;
-            }
-        }
-
-        synchronized (mUsersOnSecondaryDisplays) {
-            return mUsersOnSecondaryDisplays.get(userId, Display.INVALID_DISPLAY) == displayId;
-        }
+        return mUserVisibilityMediator.isUserVisible(userId, displayId);
     }
 
     @Override
@@ -1915,7 +1790,7 @@
                 for (int i = 0; i < usersSize; i++) {
                     UserInfo ui = mUsers.valueAt(i).info;
                     if (!ui.partial && !ui.preCreated && !mRemovingUserIds.get(ui.id)
-                            && isUserVisibleUnchecked(ui.id)) {
+                            && mUserVisibilityMediator.isUserVisible(ui.id)) {
                         visibleUsers.add(UserHandle.of(ui.id));
                     }
                 }
@@ -3394,11 +3269,39 @@
         }
     }
 
+    /** Checks whether the device is currently in headless system user mode (for any reason). */
+    @Override
+    public boolean isHeadlessSystemUserMode() {
+        synchronized (mUsersLock) {
+            final UserData systemUserData = mUsers.get(UserHandle.USER_SYSTEM);
+            return !systemUserData.info.isFull();
+        }
+    }
+
     /**
-     * Checks whether the device is really headless system user mode, ignoring system user mode
-     * emulation.
+     * Checks whether the default state of the device is headless system user mode, i.e. what the
+     * mode would be if we did a fresh factory reset.
+     * If the mode is  being emulated (via SYSTEM_USER_MODE_EMULATION_PROPERTY) then that will be
+     * returned instead.
+     * Note that, even in the absence of emulation, a device might deviate from the current default
+     * due to an OTA changing the default (which won't change the already-decided mode).
      */
-    private boolean isReallyHeadlessSystemUserMode() {
+    private boolean isDefaultHeadlessSystemUserMode() {
+        if (!Build.isDebuggable()) {
+            return RoSystemProperties.MULTIUSER_HEADLESS_SYSTEM_USER;
+        }
+
+        final String emulatedValue = SystemProperties.get(SYSTEM_USER_MODE_EMULATION_PROPERTY);
+        if (!TextUtils.isEmpty(emulatedValue)) {
+            if (UserManager.SYSTEM_USER_MODE_EMULATION_HEADLESS.equals(emulatedValue)) return true;
+            if (UserManager.SYSTEM_USER_MODE_EMULATION_FULL.equals(emulatedValue)) return false;
+            if (!UserManager.SYSTEM_USER_MODE_EMULATION_DEFAULT.equals(emulatedValue)) {
+                Slogf.e(LOG_TAG, "isDefaultHeadlessSystemUserMode(): ignoring invalid valued of "
+                                + "property %s: %s",
+                        SYSTEM_USER_MODE_EMULATION_PROPERTY, emulatedValue);
+            }
+        }
+
         return RoSystemProperties.MULTIUSER_HEADLESS_SYSTEM_USER;
     }
 
@@ -3410,30 +3313,11 @@
         if (!Build.isDebuggable()) {
             return;
         }
-
-        final String emulatedValue = SystemProperties
-                .get(UserManager.SYSTEM_USER_MODE_EMULATION_PROPERTY);
-        if (TextUtils.isEmpty(emulatedValue)) {
+        if (TextUtils.isEmpty(SystemProperties.get(SYSTEM_USER_MODE_EMULATION_PROPERTY))) {
             return;
         }
 
-        final boolean newHeadlessSystemUserMode;
-        switch (emulatedValue) {
-            case UserManager.SYSTEM_USER_MODE_EMULATION_FULL:
-                newHeadlessSystemUserMode = false;
-                break;
-            case UserManager.SYSTEM_USER_MODE_EMULATION_HEADLESS:
-                newHeadlessSystemUserMode = true;
-                break;
-            case UserManager.SYSTEM_USER_MODE_EMULATION_DEFAULT:
-                newHeadlessSystemUserMode = isReallyHeadlessSystemUserMode();
-                break;
-            default:
-                Slogf.wtf(LOG_TAG, "emulateSystemUserModeIfNeeded(): ignoring invalid valued of "
-                        + "property %s: %s", UserManager.SYSTEM_USER_MODE_EMULATION_PROPERTY,
-                        emulatedValue);
-                return;
-        }
+        final boolean newHeadlessSystemUserMode = isDefaultHeadlessSystemUserMode();
 
         // Update system user type
         synchronized (mPackagesLock) {
@@ -3469,7 +3353,7 @@
             }
         }
 
-        // Update emulated mode, which will used to triger an update on user packages
+        // Update emulated mode, which will used to trigger an update on user packages
         mUpdatingSystemUserMode = true;
     }
 
@@ -3659,7 +3543,11 @@
             synchronized (mUsersLock) {
                 UserData userData = mUsers.get(UserHandle.USER_SYSTEM);
                 userData.info.flags |= UserInfo.FLAG_SYSTEM;
-                if (!UserManager.isHeadlessSystemUserMode()) {
+                // We assume that isDefaultHeadlessSystemUserMode() does not change during the OTA
+                // from userVersion < 8 since it is documented that pre-R devices do not support its
+                // modification. Therefore, its current value should be the same as the pre-update
+                // version.
+                if (!isDefaultHeadlessSystemUserMode()) {
                     userData.info.flags |= UserInfo.FLAG_FULL;
                 }
                 userIdsToWrite.add(userData.info.id);
@@ -3868,7 +3756,7 @@
         int flags = UserInfo.FLAG_SYSTEM | UserInfo.FLAG_INITIALIZED | UserInfo.FLAG_ADMIN
                 | UserInfo.FLAG_PRIMARY;
         // Create the system user
-        String systemUserType = UserManager.isHeadlessSystemUserMode()
+        String systemUserType = isDefaultHeadlessSystemUserMode()
                 ? UserManager.USER_TYPE_SYSTEM_HEADLESS
                 : UserManager.USER_TYPE_FULL_SYSTEM;
         flags |= mUserTypes.get(systemUserType).getDefaultUserInfoFlags();
@@ -5975,6 +5863,7 @@
                 Slog.d(LOG_TAG, "updateUserIds(): userIds= " + Arrays.toString(mUserIds)
                         + " includingPreCreated=" + Arrays.toString(mUserIdsIncludingPreCreated));
             }
+            UserPackage.setValidUserIds(mUserIds);
         }
     }
 
@@ -6251,9 +6140,15 @@
         final long nowRealtime = SystemClock.elapsedRealtime();
         final StringBuilder sb = new StringBuilder();
 
-        if (args != null && args.length > 0 && args[0].equals("--user")) {
-            dumpUser(pw, UserHandle.parseUserArg(args[1]), sb, now, nowRealtime);
-            return;
+        if (args != null && args.length > 0) {
+            switch (args[0]) {
+                case "--user":
+                    dumpUser(pw, UserHandle.parseUserArg(args[1]), sb, now, nowRealtime);
+                    return;
+                case "--visibility-mediator":
+                    mUserVisibilityMediator.dump(pw);
+                    return;
+            }
         }
 
         final int currentUserId = getCurrentUserId();
@@ -6316,18 +6211,9 @@
             }
         } // synchronized (mPackagesLock)
 
-        // Multiple Users on Multiple Display info
-        pw.println("  Supports users on secondary displays: " + mUsersOnSecondaryDisplaysEnabled);
-        // mUsersOnSecondaryDisplaysEnabled is set on constructor, while the UserManager API is
-        // set dynamically, so print both to help cases where the developer changed it on the fly
-        pw.println("  UM.isUsersOnSecondaryDisplaysEnabled(): "
-                + UserManager.isUsersOnSecondaryDisplaysEnabled());
-        if (mUsersOnSecondaryDisplaysEnabled) {
-            pw.print("  Users on secondary displays: ");
-            synchronized (mUsersOnSecondaryDisplays) {
-                pw.println(mUsersOnSecondaryDisplays);
-            }
-        }
+        pw.println();
+        mUserVisibilityMediator.dump(pw);
+        pw.println();
 
         // Dump some capabilities
         pw.println();
@@ -6338,9 +6224,12 @@
                 com.android.internal.R.bool.config_guestUserEphemeral));
         pw.println("  Force ephemeral users: " + mForceEphemeralUsers);
         pw.println("  Is split-system user: " + UserManager.isSplitSystemUser());
-        final boolean isHeadlessSystemUserMode = UserManager.isHeadlessSystemUserMode();
+        final boolean isHeadlessSystemUserMode = isHeadlessSystemUserMode();
         pw.println("  Is headless-system mode: " + isHeadlessSystemUserMode);
-        if (isHeadlessSystemUserMode != isReallyHeadlessSystemUserMode()) {
+        if (isHeadlessSystemUserMode != RoSystemProperties.MULTIUSER_HEADLESS_SYSTEM_USER) {
+            pw.println("  (differs from the current default build value)");
+        }
+        if (!TextUtils.isEmpty(SystemProperties.get(SYSTEM_USER_MODE_EMULATION_PROPERTY))) {
             pw.println("  (emulated by 'cmd user set-system-user-mode-emulation')");
             if (mUpdatingSystemUserMode) {
                 pw.println("  (and being updated after boot)");
@@ -6899,133 +6788,33 @@
         }
 
         @Override
-        public void assignUserToDisplay(int userId, int displayId) {
-            if (DBG_MUMD) {
-                Slogf.d(LOG_TAG, "assignUserToDisplay(%d, %d)", userId, displayId);
-            }
-
-            // NOTE: Using Boolean instead of boolean as it will be re-used below
-            Boolean isProfile = null;
-            if (displayId == Display.DEFAULT_DISPLAY) {
-                if (mUsersOnSecondaryDisplaysEnabled) {
-                    // Profiles are only supported in the default display, but it cannot return yet
-                    // as it needs to check if the parent is also assigned to the DEFAULT_DISPLAY
-                    // (this is done indirectly below when it checks that the profile parent is the
-                    // current user, as the current user is always assigned to the DEFAULT_DISPLAY).
-                    isProfile = isProfileUnchecked(userId);
-                }
-                if (isProfile == null || !isProfile) {
-                    // Don't need to do anything because methods (such as isUserVisible()) already
-                    // know that the current user (and their profiles) is assigned to the default
-                    // display.
-                    if (DBG_MUMD) {
-                        Slogf.d(LOG_TAG, "ignoring on default display");
-                    }
-                    return;
-                }
-            }
-
-            if (!mUsersOnSecondaryDisplaysEnabled) {
-                throw new UnsupportedOperationException("assignUserToDisplay(" + userId + ", "
-                        + displayId + ") called on device that doesn't support multiple "
-                        + "users on multiple displays");
-            }
-
-            Preconditions.checkArgument(userId != UserHandle.USER_SYSTEM, "Cannot assign system "
-                    + "user to secondary display (%d)", displayId);
-            Preconditions.checkArgument(displayId != Display.INVALID_DISPLAY,
-                    "Cannot assign to INVALID_DISPLAY (%d)", displayId);
-
-            int currentUserId = getCurrentUserId();
-            Preconditions.checkArgument(userId != currentUserId,
-                    "Cannot assign current user (%d) to other displays", currentUserId);
-
-            if (isProfile == null) {
-                isProfile = isProfileUnchecked(userId);
-            }
-            synchronized (mUsersOnSecondaryDisplays) {
-                if (isProfile) {
-                    // Profile can only start in the same display as parent. And for simplicity,
-                    // that display must be the DEFAULT_DISPLAY.
-                    Preconditions.checkArgument(displayId == Display.DEFAULT_DISPLAY,
-                            "Profile user can only be started in the default display");
-                    int parentUserId = getProfileParentId(userId);
-                    Preconditions.checkArgument(parentUserId == currentUserId,
-                            "Only profile of current user can be assigned to a display");
-                    if (DBG_MUMD) {
-                        Slogf.d(LOG_TAG, "Ignoring profile user %d on default display", userId);
-                    }
-                    return;
-                }
-
-                // Check if display is available
-                for (int i = 0; i < mUsersOnSecondaryDisplays.size(); i++) {
-                    int assignedUserId = mUsersOnSecondaryDisplays.keyAt(i);
-                    int assignedDisplayId = mUsersOnSecondaryDisplays.valueAt(i);
-                    if (DBG_MUMD) {
-                        Slogf.d(LOG_TAG, "%d: assignedUserId=%d, assignedDisplayId=%d",
-                                i, assignedUserId, assignedDisplayId);
-                    }
-                    if (displayId == assignedDisplayId) {
-                        throw new IllegalStateException("Cannot assign user " + userId + " to "
-                                + "display " + displayId + " because such display is already "
-                                + "assigned to user " + assignedUserId);
-                    }
-                    if (userId == assignedUserId) {
-                        throw new IllegalStateException("Cannot assign user " + userId + " to "
-                                + "display " + displayId + " because such user is as already "
-                                + "assigned to display " + assignedDisplayId);
-                    }
-                }
-
-                if (DBG_MUMD) {
-                    Slogf.d(LOG_TAG, "Adding full user %d -> display %d", userId, displayId);
-                }
-                mUsersOnSecondaryDisplays.put(userId, displayId);
-            }
+        public void assignUserToDisplay(@UserIdInt int userId, int displayId) {
+            mUserVisibilityMediator.assignUserToDisplay(userId, displayId);
         }
 
         @Override
         public void unassignUserFromDisplay(@UserIdInt int userId) {
-            if (DBG_MUMD) {
-                Slogf.d(LOG_TAG, "unassignUserFromDisplay(%d)", userId);
-            }
-            if (!mUsersOnSecondaryDisplaysEnabled) {
-                // Don't need to do anything because methods (such as isUserVisible()) already know
-                // that the current user (and their profiles) is assigned to the default display.
-                if (DBG_MUMD) {
-                    Slogf.d(LOG_TAG, "ignoring when device doesn't support MUMD");
-                }
-                return;
-            }
-
-            synchronized (mUsersOnSecondaryDisplays) {
-                if (DBG_MUMD) {
-                    Slogf.d(LOG_TAG, "Removing %d from mUsersOnSecondaryDisplays (%s)", userId,
-                            mUsersOnSecondaryDisplays);
-                }
-                mUsersOnSecondaryDisplays.delete(userId);
-            }
+            mUserVisibilityMediator.unassignUserFromDisplay(userId);
         }
 
         @Override
-        public boolean isUserVisible(int userId) {
-            return isUserVisibleUnchecked(userId);
+        public boolean isUserVisible(@UserIdInt int userId) {
+            return mUserVisibilityMediator.isUserVisible(userId);
         }
 
         @Override
-        public boolean isUserVisible(int userId, int displayId) {
-            return isUserVisibleOnDisplay(userId, displayId);
+        public boolean isUserVisible(@UserIdInt int userId, int displayId) {
+            return mUserVisibilityMediator.isUserVisible(userId, displayId);
         }
 
         @Override
-        public int getDisplayAssignedToUser(int userId) {
-            return UserManagerService.this.getDisplayAssignedToUser(userId);
+        public int getDisplayAssignedToUser(@UserIdInt int userId) {
+            return mUserVisibilityMediator.getDisplayAssignedToUser(userId);
         }
 
         @Override
-        public int getUserAssignedToDisplay(int displayId) {
-            return UserManagerService.this.getUserAssignedToDisplay(displayId);
+        public @UserIdInt int getUserAssignedToDisplay(int displayId) {
+            return mUserVisibilityMediator.getUserAssignedToDisplay(displayId);
         }
     } // class LocalService
 
diff --git a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
index d1f3341e..dbd026e 100644
--- a/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
+++ b/services/core/java/com/android/server/pm/UserRestrictionsUtils.java
@@ -147,7 +147,8 @@
             UserManager.DISALLOW_WIFI_TETHERING,
             UserManager.DISALLOW_SHARING_ADMIN_CONFIGURED_WIFI,
             UserManager.DISALLOW_WIFI_DIRECT,
-            UserManager.DISALLOW_ADD_WIFI_CONFIG
+            UserManager.DISALLOW_ADD_WIFI_CONFIG,
+            UserManager.DISALLOW_CELLULAR_2G
     });
 
     public static final Set<String> DEPRECATED_USER_RESTRICTIONS = Sets.newArraySet(
@@ -195,7 +196,8 @@
             UserManager.DISALLOW_CHANGE_WIFI_STATE,
             UserManager.DISALLOW_WIFI_TETHERING,
             UserManager.DISALLOW_WIFI_DIRECT,
-            UserManager.DISALLOW_ADD_WIFI_CONFIG
+            UserManager.DISALLOW_ADD_WIFI_CONFIG,
+            UserManager.DISALLOW_CELLULAR_2G
     );
 
     /**
@@ -234,7 +236,8 @@
                     UserManager.DISALLOW_CHANGE_WIFI_STATE,
                     UserManager.DISALLOW_WIFI_TETHERING,
                     UserManager.DISALLOW_WIFI_DIRECT,
-                    UserManager.DISALLOW_ADD_WIFI_CONFIG
+                    UserManager.DISALLOW_ADD_WIFI_CONFIG,
+                    UserManager.DISALLOW_CELLULAR_2G
     );
 
     /**
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index b98d20e..5b6196f 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -128,7 +128,8 @@
                 .setIsCredentialSharableWithParent(true)
                 .setDefaultUserProperties(new UserProperties.Builder()
                         .setStartWithParent(true)
-                        .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_WITH_PARENT));
+                        .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_WITH_PARENT)
+                        .setShowInSettings(UserProperties.SHOW_IN_SETTINGS_WITH_PARENT));
     }
 
     /**
@@ -163,7 +164,8 @@
                 .setIsCredentialSharableWithParent(true)
                 .setDefaultUserProperties(new UserProperties.Builder()
                         .setStartWithParent(true)
-                        .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE));
+                        .setShowInLauncher(UserProperties.SHOW_IN_LAUNCHER_SEPARATE)
+                        .setShowInSettings(UserProperties.SHOW_IN_SETTINGS_SEPARATE));
     }
 
     /**
diff --git a/services/core/java/com/android/server/pm/UserVisibilityMediator.java b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
new file mode 100644
index 0000000..f725c48
--- /dev/null
+++ b/services/core/java/com/android/server/pm/UserVisibilityMediator.java
@@ -0,0 +1,350 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.pm;
+
+import android.annotation.Nullable;
+import android.annotation.UserIdInt;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.util.IndentingPrintWriter;
+import android.util.SparseIntArray;
+import android.view.Display;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.Preconditions;
+import com.android.server.utils.Slogf;
+
+import java.io.PrintWriter;
+
+/**
+ * Class responsible for deciding whether a user is visible (or visible for a given display).
+ *
+ * <p>This class is thread safe.
+ */
+// TODO(b/244644281): improve javadoc (for example, explain all cases / modes)
+public final class UserVisibilityMediator {
+
+    private static final boolean DBG = false; // DO NOT SUBMIT WITH TRUE
+
+    private static final String TAG = UserVisibilityMediator.class.getSimpleName();
+
+    private final Object mLock = new Object();
+
+    // TODO(b/244644281): should not depend on service, but keep its own internal state (like
+    // current user and profile groups), but it is initially as the code was just moved from UMS
+    // "as is". Similarly, it shouldn't need to pass the SparseIntArray on constructor (which was
+    // added to UMS for testing purposes)
+    private final UserManagerService mService;
+
+    private final boolean mUsersOnSecondaryDisplaysEnabled;
+
+    @Nullable
+    @GuardedBy("mLock")
+    private final SparseIntArray mUsersOnSecondaryDisplays;
+
+    UserVisibilityMediator(UserManagerService service) {
+        this(service, UserManager.isUsersOnSecondaryDisplaysEnabled(),
+                /* usersOnSecondaryDisplays= */ null);
+    }
+
+    @VisibleForTesting
+    UserVisibilityMediator(UserManagerService service, boolean usersOnSecondaryDisplaysEnabled,
+            @Nullable SparseIntArray usersOnSecondaryDisplays) {
+        mService = service;
+        mUsersOnSecondaryDisplaysEnabled = usersOnSecondaryDisplaysEnabled;
+        if (mUsersOnSecondaryDisplaysEnabled) {
+            mUsersOnSecondaryDisplays = usersOnSecondaryDisplays == null
+                    ? new SparseIntArray() // default behavior
+                    : usersOnSecondaryDisplays; // passed by unit test
+        } else {
+            mUsersOnSecondaryDisplays = null;
+        }
+    }
+
+    /**
+     * See {@link UserManagerInternal#assignUserToDisplay(int, int)}.
+     */
+    public void assignUserToDisplay(int userId, int displayId) {
+        if (DBG) {
+            Slogf.d(TAG, "assignUserToDisplay(%d, %d)", userId, displayId);
+        }
+
+        // NOTE: Using Boolean instead of boolean as it will be re-used below
+        Boolean isProfile = null;
+        if (displayId == Display.DEFAULT_DISPLAY) {
+            if (mUsersOnSecondaryDisplaysEnabled) {
+                // Profiles are only supported in the default display, but it cannot return yet
+                // as it needs to check if the parent is also assigned to the DEFAULT_DISPLAY
+                // (this is done indirectly below when it checks that the profile parent is the
+                // current user, as the current user is always assigned to the DEFAULT_DISPLAY).
+                isProfile = isProfileUnchecked(userId);
+            }
+            if (isProfile == null || !isProfile) {
+                // Don't need to do anything because methods (such as isUserVisible()) already
+                // know that the current user (and their profiles) is assigned to the default
+                // display.
+                if (DBG) {
+                    Slogf.d(TAG, "ignoring on default display");
+                }
+                return;
+            }
+        }
+
+        if (!mUsersOnSecondaryDisplaysEnabled) {
+            throw new UnsupportedOperationException("assignUserToDisplay(" + userId + ", "
+                    + displayId + ") called on device that doesn't support multiple "
+                    + "users on multiple displays");
+        }
+
+        Preconditions.checkArgument(userId != UserHandle.USER_SYSTEM, "Cannot assign system "
+                + "user to secondary display (%d)", displayId);
+        Preconditions.checkArgument(displayId != Display.INVALID_DISPLAY,
+                "Cannot assign to INVALID_DISPLAY (%d)", displayId);
+
+        int currentUserId = getCurrentUserId();
+        Preconditions.checkArgument(userId != currentUserId,
+                "Cannot assign current user (%d) to other displays", currentUserId);
+
+        if (isProfile == null) {
+            isProfile = isProfileUnchecked(userId);
+        }
+        synchronized (mLock) {
+            if (isProfile) {
+                // Profile can only start in the same display as parent. And for simplicity,
+                // that display must be the DEFAULT_DISPLAY.
+                Preconditions.checkArgument(displayId == Display.DEFAULT_DISPLAY,
+                        "Profile user can only be started in the default display");
+                int parentUserId = getProfileParentId(userId);
+                Preconditions.checkArgument(parentUserId == currentUserId,
+                        "Only profile of current user can be assigned to a display");
+                if (DBG) {
+                    Slogf.d(TAG, "Ignoring profile user %d on default display", userId);
+                }
+                return;
+            }
+
+            // Check if display is available
+            for (int i = 0; i < mUsersOnSecondaryDisplays.size(); i++) {
+                int assignedUserId = mUsersOnSecondaryDisplays.keyAt(i);
+                int assignedDisplayId = mUsersOnSecondaryDisplays.valueAt(i);
+                if (DBG) {
+                    Slogf.d(TAG, "%d: assignedUserId=%d, assignedDisplayId=%d",
+                            i, assignedUserId, assignedDisplayId);
+                }
+                if (displayId == assignedDisplayId) {
+                    throw new IllegalStateException("Cannot assign user " + userId + " to "
+                            + "display " + displayId + " because such display is already "
+                            + "assigned to user " + assignedUserId);
+                }
+                if (userId == assignedUserId) {
+                    throw new IllegalStateException("Cannot assign user " + userId + " to "
+                            + "display " + displayId + " because such user is as already "
+                            + "assigned to display " + assignedDisplayId);
+                }
+            }
+
+            if (DBG) {
+                Slogf.d(TAG, "Adding full user %d -> display %d", userId, displayId);
+            }
+            mUsersOnSecondaryDisplays.put(userId, displayId);
+        }
+    }
+
+    /**
+     * See {@link UserManagerInternal#unassignUserFromDisplay(int)}.
+     */
+    public void unassignUserFromDisplay(int userId) {
+        if (DBG) {
+            Slogf.d(TAG, "unassignUserFromDisplay(%d)", userId);
+        }
+        if (!mUsersOnSecondaryDisplaysEnabled) {
+            // Don't need to do anything because methods (such as isUserVisible()) already know
+            // that the current user (and their profiles) is assigned to the default display.
+            if (DBG) {
+                Slogf.d(TAG, "ignoring when device doesn't support MUMD");
+            }
+            return;
+        }
+
+        synchronized (mLock) {
+            if (DBG) {
+                Slogf.d(TAG, "Removing %d from mUsersOnSecondaryDisplays (%s)", userId,
+                        mUsersOnSecondaryDisplays);
+            }
+            mUsersOnSecondaryDisplays.delete(userId);
+        }
+    }
+
+    /**
+     * See {@link UserManagerInternal#isUserVisible(int)}.
+     */
+    public boolean isUserVisible(int userId) {
+        // First check current foreground user and their profiles (on main display)
+        if (isCurrentUserOrRunningProfileOfCurrentUser(userId)) {
+            return true;
+        }
+
+        // Device doesn't support multiple users on multiple displays, so only users checked above
+        // can be visible
+        if (!mUsersOnSecondaryDisplaysEnabled) {
+            return false;
+        }
+
+        synchronized (mLock) {
+            return mUsersOnSecondaryDisplays.indexOfKey(userId) >= 0;
+        }
+    }
+
+    /**
+     * See {@link UserManagerInternal#isUserVisible(int, int)}.
+     */
+    public boolean isUserVisible(int userId, int displayId) {
+        if (displayId == Display.INVALID_DISPLAY) {
+            return false;
+        }
+        if (!mUsersOnSecondaryDisplaysEnabled) {
+            return isCurrentUserOrRunningProfileOfCurrentUser(userId);
+        }
+
+        // TODO(b/244644281): temporary workaround to let WM use this API without breaking current
+        // behavior - return true for current user / profile for any display (other than those
+        // explicitly assigned to another users), otherwise they wouldn't be able to launch
+        // activities on other non-passenger displays, like cluster, display, or virtual displays).
+        // In the long-term, it should rely just on mUsersOnSecondaryDisplays, which
+        // would be updated by DisplayManagerService when displays are created / initialized.
+        if (isCurrentUserOrRunningProfileOfCurrentUser(userId)) {
+            synchronized (mLock) {
+                boolean assignedToUser = false;
+                boolean assignedToAnotherUser = false;
+                for (int i = 0; i < mUsersOnSecondaryDisplays.size(); i++) {
+                    if (mUsersOnSecondaryDisplays.valueAt(i) == displayId) {
+                        if (mUsersOnSecondaryDisplays.keyAt(i) == userId) {
+                            assignedToUser = true;
+                            break;
+                        } else {
+                            assignedToAnotherUser = true;
+                            // Cannot break because it could be assigned to a profile of the user
+                            // (and we better not assume that the iteration will check for the
+                            // parent user before its profiles)
+                        }
+                    }
+                }
+                if (DBG) {
+                    Slogf.d(TAG, "isUserVisibleOnDisplay(%d, %d): assignedToUser=%b, "
+                            + "assignedToAnotherUser=%b, mUsersOnSecondaryDisplays=%s",
+                            userId, displayId, assignedToUser, assignedToAnotherUser,
+                            mUsersOnSecondaryDisplays);
+                }
+                return assignedToUser || !assignedToAnotherUser;
+            }
+        }
+
+        synchronized (mLock) {
+            return mUsersOnSecondaryDisplays.get(userId, Display.INVALID_DISPLAY) == displayId;
+        }
+    }
+
+    /**
+     * See {@link UserManagerInternal#getDisplayAssignedToUser(int)}.
+     */
+    public int getDisplayAssignedToUser(int userId) {
+        if (isCurrentUserOrRunningProfileOfCurrentUser(userId)) {
+            return Display.DEFAULT_DISPLAY;
+        }
+
+        if (!mUsersOnSecondaryDisplaysEnabled) {
+            return Display.INVALID_DISPLAY;
+        }
+
+        synchronized (mLock) {
+            return mUsersOnSecondaryDisplays.get(userId, Display.INVALID_DISPLAY);
+        }
+    }
+
+    /**
+     * See {@link UserManagerInternal#getUserAssignedToDisplay(int)}.
+     */
+    public int getUserAssignedToDisplay(int displayId) {
+        if (displayId == Display.DEFAULT_DISPLAY || !mUsersOnSecondaryDisplaysEnabled) {
+            return getCurrentUserId();
+        }
+
+        synchronized (mLock) {
+            for (int i = 0; i < mUsersOnSecondaryDisplays.size(); i++) {
+                if (mUsersOnSecondaryDisplays.valueAt(i) != displayId) {
+                    continue;
+                }
+                int userId = mUsersOnSecondaryDisplays.keyAt(i);
+                if (!isProfileUnchecked(userId)) {
+                    return userId;
+                } else if (DBG) {
+                    Slogf.d(TAG, "getUserAssignedToDisplay(%d): skipping user %d because it's "
+                            + "a profile", displayId, userId);
+                }
+            }
+        }
+
+        int currentUserId = getCurrentUserId();
+        if (DBG) {
+            Slogf.d(TAG, "getUserAssignedToDisplay(%d): no user assigned to display, returning "
+                    + "current user (%d) instead", displayId, currentUserId);
+        }
+        return currentUserId;
+    }
+
+    private void dump(IndentingPrintWriter ipw) {
+        ipw.println("UserVisibilityManager");
+        ipw.increaseIndent();
+
+        ipw.print("Supports users on secondary displays: ");
+        ipw.println(mUsersOnSecondaryDisplaysEnabled);
+
+        if (mUsersOnSecondaryDisplaysEnabled) {
+            ipw.print("Users on secondary displays: ");
+            synchronized (mLock) {
+                ipw.println(mUsersOnSecondaryDisplays);
+            }
+        }
+
+        ipw.decreaseIndent();
+    }
+
+    void dump(PrintWriter pw) {
+        if (pw instanceof IndentingPrintWriter) {
+            dump((IndentingPrintWriter) pw);
+            return;
+        }
+        dump(new IndentingPrintWriter(pw));
+    }
+
+    // TODO(b/244644281): remove methods below once this class caches that state
+    private @UserIdInt int getCurrentUserId() {
+        return mService.getCurrentUserId();
+    }
+
+    private boolean isCurrentUserOrRunningProfileOfCurrentUser(@UserIdInt int userId) {
+        return mService.isCurrentUserOrRunningProfileOfCurrentUser(userId);
+    }
+
+    private boolean isProfileUnchecked(@UserIdInt int userId) {
+        return mService.isProfileUnchecked(userId);
+    }
+
+    private @UserIdInt int getProfileParentId(@UserIdInt int userId) {
+        return mService.getProfileParentId(userId);
+    }
+}
diff --git a/services/core/java/com/android/server/pm/dex/OdsignStatsLogger.java b/services/core/java/com/android/server/pm/dex/OdsignStatsLogger.java
index fa08add..227a3a1 100644
--- a/services/core/java/com/android/server/pm/dex/OdsignStatsLogger.java
+++ b/services/core/java/com/android/server/pm/dex/OdsignStatsLogger.java
@@ -39,6 +39,7 @@
     // These need to be kept in sync with system/security/ondevice-signing/StatsReporter.{h, cpp}.
     private static final String METRICS_FILE = "/data/misc/odsign/metrics/odsign-metrics.txt";
     private static final String COMPOS_METRIC_NAME = "comp_os_artifacts_check_record";
+    private static final String ODSIGN_METRIC_NAME = "odsign_record";
 
     /**
      * Arrange for stats to be uploaded in the background.
@@ -64,18 +65,45 @@
             for (String line : lines.split("\n")) {
                 String[] metrics = line.split(" ");
 
-                if (metrics.length != 4 || !metrics[0].equals(COMPOS_METRIC_NAME)) {
-                    Slog.w(TAG, "Malformed metrics file");
-                    break;
+                if (line.isEmpty() || metrics.length < 1) {
+                    Slog.w(TAG, "Empty metrics line");
+                    continue;
                 }
 
-                boolean currentArtifactsOk = metrics[1].equals("1");
-                boolean compOsPendingArtifactsExists = metrics[2].equals("1");
-                boolean useCompOsGeneratedArtifacts = metrics[3].equals("1");
+                switch (metrics[0]) {
+                    case COMPOS_METRIC_NAME: {
+                        if (metrics.length != 4) {
+                            Slog.w(TAG, "Malformed CompOS metrics line '" + line + "'");
+                            continue;
+                        }
 
-                ArtStatsLog.write(ArtStatsLog.EARLY_BOOT_COMP_OS_ARTIFACTS_CHECK_REPORTED,
-                        currentArtifactsOk, compOsPendingArtifactsExists,
-                        useCompOsGeneratedArtifacts);
+                        boolean currentArtifactsOk = metrics[1].equals("1");
+                        boolean compOsPendingArtifactsExists = metrics[2].equals("1");
+                        boolean useCompOsGeneratedArtifacts = metrics[3].equals("1");
+
+                        ArtStatsLog.write(ArtStatsLog.EARLY_BOOT_COMP_OS_ARTIFACTS_CHECK_REPORTED,
+                                currentArtifactsOk, compOsPendingArtifactsExists,
+                                useCompOsGeneratedArtifacts);
+                        break;
+                    }
+                    case ODSIGN_METRIC_NAME: {
+                        if (metrics.length != 2) {
+                            Slog.w(TAG, "Malformed odsign metrics line '" + line + "'");
+                            continue;
+                        }
+
+                        try {
+                            int status = Integer.parseInt(metrics[1]);
+                            ArtStatsLog.write(ArtStatsLog.ODSIGN_REPORTED, status);
+                        } catch (NumberFormatException e) {
+                            Slog.w(TAG, "Malformed odsign metrics line '" + line + "'");
+                        }
+
+                        break;
+                    }
+                    default:
+                        Slog.w(TAG, "Malformed metrics line '" + line + "'");
+                }
             }
         } catch (FileNotFoundException e) {
             // This is normal and probably means no new metrics have been generated.
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index 8772de3..a7d4cea 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -20,7 +20,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
-import android.apex.ApexInfo;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.Attribution;
@@ -79,11 +78,8 @@
 import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 import com.android.server.pm.pkg.parsing.ParsingUtils;
 
-import libcore.util.EmptyArray;
-
 import java.io.File;
 import java.util.ArrayList;
-import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -109,21 +105,9 @@
     public static PackageInfo generate(AndroidPackage pkg, int[] gids,
             @PackageManager.PackageInfoFlagsBits long flags, long firstInstallTime,
             long lastUpdateTime, Set<String> grantedPermissions, PackageUserStateInternal state,
-            @UserIdInt int userId, @Nullable PackageStateInternal pkgSetting) {
+            @UserIdInt int userId, @NonNull PackageStateInternal pkgSetting) {
         return generateWithComponents(pkg, gids, flags, firstInstallTime, lastUpdateTime,
-                grantedPermissions, state, userId, null, pkgSetting);
-    }
-
-    /**
-     * @param pkgSetting See {@link PackageInfoUtils} for description of pkgSetting usage.
-     * @deprecated Once ENABLE_FEATURE_SCAN_APEX is removed, this should also be removed.
-     */
-    @Deprecated
-    @Nullable
-    public static PackageInfo generate(AndroidPackage pkg, ApexInfo apexInfo, long flags,
-            @Nullable PackageStateInternal pkgSetting, @UserIdInt int userId) {
-        return generateWithComponents(pkg, EmptyArray.INT, flags, 0, 0, Collections.emptySet(),
-                PackageUserStateInternal.DEFAULT, userId, apexInfo, pkgSetting);
+                grantedPermissions, state, userId, pkgSetting);
     }
 
     /**
@@ -132,8 +116,7 @@
     private static PackageInfo generateWithComponents(AndroidPackage pkg, int[] gids,
             @PackageManager.PackageInfoFlagsBits long flags, long firstInstallTime,
             long lastUpdateTime, Set<String> grantedPermissions, PackageUserStateInternal state,
-            @UserIdInt int userId, @Nullable ApexInfo apexInfo,
-            @Nullable PackageStateInternal pkgSetting) {
+            @UserIdInt int userId, @NonNull PackageStateInternal pkgSetting) {
         ApplicationInfo applicationInfo = generateApplicationInfo(pkg, flags, state, userId,
                 pkgSetting);
         if (applicationInfo == null) {
@@ -247,22 +230,6 @@
                     &= ~ApplicationInfo.PRIVATE_FLAG_EXT_ATTRIBUTIONS_ARE_USER_VISIBLE;
         }
 
-        if (apexInfo != null) {
-            File apexFile = new File(apexInfo.modulePath);
-
-            info.applicationInfo.sourceDir = apexFile.getPath();
-            info.applicationInfo.publicSourceDir = apexFile.getPath();
-            info.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
-            info.applicationInfo.flags |= ApplicationInfo.FLAG_INSTALLED;
-            if (apexInfo.isFactory) {
-                info.applicationInfo.flags &= ~ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
-            } else {
-                info.applicationInfo.flags |= ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
-            }
-            info.isApex = true;
-            info.isActiveApex = apexInfo.isActive;
-        }
-
         final SigningDetails signingDetails = pkg.getSigningDetails();
         // deprecated method of getting signing certificates
         if ((flags & PackageManager.GET_SIGNATURES) != 0) {
@@ -294,7 +261,7 @@
         info.coreApp = pkg.isCoreApp();
         info.isApex = pkg.isApex();
 
-        if (pkgSetting != null && !pkgSetting.hasSharedUser()) {
+        if (!pkgSetting.hasSharedUser()) {
             // It is possible that this shared UID app has left
             info.sharedUserId = null;
             info.sharedUserLabel = 0;
@@ -452,7 +419,7 @@
     public static ApplicationInfo generateApplicationInfo(AndroidPackage pkg,
             @PackageManager.ApplicationInfoFlagsBits long flags,
             @NonNull PackageUserStateInternal state, @UserIdInt int userId,
-            @Nullable PackageStateInternal pkgSetting) {
+            @NonNull PackageStateInternal pkgSetting) {
         if (pkg == null) {
             return null;
         }
@@ -463,35 +430,31 @@
         }
 
         // Make shallow copy so we can store the metadata/libraries safely
-        ApplicationInfo info = AndroidPackageUtils.toAppInfoWithoutState(pkg);
+        ApplicationInfo info = AndroidPackageUtils.generateAppInfoWithoutState(pkg);
 
         updateApplicationInfo(info, flags, state);
 
         initForUser(info, pkg, userId);
 
-        if (pkgSetting != null) {
-            // TODO(b/135203078): Remove PackageParser1/toAppInfoWithoutState and clean all this up
-            PackageStateUnserialized pkgState = pkgSetting.getTransientState();
-            info.hiddenUntilInstalled = pkgState.isHiddenUntilInstalled();
-            List<String> usesLibraryFiles = pkgState.getUsesLibraryFiles();
-            var usesLibraries = pkgState.getUsesLibraryInfos();
-            var usesLibraryInfos = new ArrayList<SharedLibraryInfo>();
-            for (int index = 0; index < usesLibraries.size(); index++) {
-                usesLibraryInfos.add(usesLibraries.get(index).getInfo());
-            }
-            info.sharedLibraryFiles = usesLibraryFiles.isEmpty()
-                    ? null : usesLibraryFiles.toArray(new String[0]);
-            info.sharedLibraryInfos = usesLibraryInfos.isEmpty() ? null : usesLibraryInfos;
-            if (info.category == ApplicationInfo.CATEGORY_UNDEFINED) {
-                info.category = pkgSetting.getCategoryOverride();
-            }
+        // TODO(b/135203078): Remove PackageParser1/toAppInfoWithoutState and clean all this up
+        PackageStateUnserialized pkgState = pkgSetting.getTransientState();
+        info.hiddenUntilInstalled = pkgState.isHiddenUntilInstalled();
+        List<String> usesLibraryFiles = pkgState.getUsesLibraryFiles();
+        var usesLibraries = pkgState.getUsesLibraryInfos();
+        var usesLibraryInfos = new ArrayList<SharedLibraryInfo>();
+        for (int index = 0; index < usesLibraries.size(); index++) {
+            usesLibraryInfos.add(usesLibraries.get(index).getInfo());
+        }
+        info.sharedLibraryFiles = usesLibraryFiles.isEmpty()
+                ? null : usesLibraryFiles.toArray(new String[0]);
+        info.sharedLibraryInfos = usesLibraryInfos.isEmpty() ? null : usesLibraryInfos;
+        if (info.category == ApplicationInfo.CATEGORY_UNDEFINED) {
+            info.category = pkgSetting.getCategoryOverride();
         }
 
         info.seInfo = AndroidPackageUtils.getSeInfo(pkg, pkgSetting);
-        info.primaryCpuAbi = pkgSetting == null ? AndroidPackageUtils.getRawPrimaryCpuAbi(pkg)
-                : pkgSetting.getPrimaryCpuAbi();
-        info.secondaryCpuAbi = pkgSetting == null ? AndroidPackageUtils.getRawSecondaryCpuAbi(pkg)
-                : pkgSetting.getSecondaryCpuAbi();
+        info.primaryCpuAbi = pkgSetting.getPrimaryCpuAbi();
+        info.secondaryCpuAbi = pkgSetting.getSecondaryCpuAbi();
 
         info.flags |= appInfoFlags(info.flags, pkgSetting);
         info.privateFlags |= appInfoPrivateFlags(info.privateFlags, pkgSetting);
@@ -508,7 +471,7 @@
     public static ActivityInfo generateActivityInfo(AndroidPackage pkg, ParsedActivity a,
             @PackageManager.ComponentInfoFlagsBits long flags,
             @NonNull PackageUserStateInternal state, @UserIdInt int userId,
-            @Nullable PackageStateInternal pkgSetting) {
+            @NonNull PackageStateInternal pkgSetting) {
         return generateActivityInfo(pkg, a, flags, state, null, userId, pkgSetting);
     }
 
@@ -520,7 +483,7 @@
     public static ActivityInfo generateActivityInfo(AndroidPackage pkg, ParsedActivity a,
             @PackageManager.ComponentInfoFlagsBits long flags,
             @NonNull PackageUserStateInternal state, @Nullable ApplicationInfo applicationInfo,
-            @UserIdInt int userId, @Nullable PackageStateInternal pkgSetting) {
+            @UserIdInt int userId, @NonNull PackageStateInternal pkgSetting) {
         if (a == null) return null;
         if (!checkUseInstalledOrHidden(pkg, pkgSetting, state, flags)) {
             return null;
@@ -597,7 +560,7 @@
     @Nullable
     public static ServiceInfo generateServiceInfo(AndroidPackage pkg, ParsedService s,
             @PackageManager.ComponentInfoFlagsBits long flags, PackageUserStateInternal state,
-            @UserIdInt int userId, @Nullable PackageStateInternal pkgSetting) {
+            @UserIdInt int userId, @NonNull PackageStateInternal pkgSetting) {
         return generateServiceInfo(pkg, s, flags, state, null, userId, pkgSetting);
     }
 
@@ -609,7 +572,7 @@
     public static ServiceInfo generateServiceInfo(AndroidPackage pkg, ParsedService s,
             @PackageManager.ComponentInfoFlagsBits long flags, PackageUserStateInternal state,
             @Nullable ApplicationInfo applicationInfo, int userId,
-            @Nullable PackageStateInternal pkgSetting) {
+            @NonNull PackageStateInternal pkgSetting) {
         if (s == null) return null;
         if (!checkUseInstalledOrHidden(pkg, pkgSetting, state, flags)) {
             return null;
@@ -647,7 +610,7 @@
     public static ProviderInfo generateProviderInfo(AndroidPackage pkg, ParsedProvider p,
             @PackageManager.ComponentInfoFlagsBits long flags, PackageUserStateInternal state,
             @NonNull ApplicationInfo applicationInfo, int userId,
-            @Nullable PackageStateInternal pkgSetting) {
+            @NonNull PackageStateInternal pkgSetting) {
         if (p == null) return null;
         if (!checkUseInstalledOrHidden(pkg, pkgSetting, state, flags)) {
             return null;
@@ -696,7 +659,7 @@
     @Nullable
     public static InstrumentationInfo generateInstrumentationInfo(ParsedInstrumentation i,
             AndroidPackage pkg, @PackageManager.ComponentInfoFlagsBits long flags,
-            PackageUserStateInternal state, int userId, @Nullable PackageStateInternal pkgSetting) {
+            PackageUserStateInternal state, int userId, @NonNull PackageStateInternal pkgSetting) {
         if (i == null) return null;
         if (!checkUseInstalledOrHidden(pkg, pkgSetting, state, flags)) {
             return null;
@@ -719,10 +682,8 @@
 
         initForUser(info, pkg, userId);
 
-        info.primaryCpuAbi = pkgSetting == null ? AndroidPackageUtils.getRawPrimaryCpuAbi(pkg)
-                : pkgSetting.getPrimaryCpuAbi();
-        info.secondaryCpuAbi = pkgSetting == null ? AndroidPackageUtils.getRawSecondaryCpuAbi(pkg)
-                : pkgSetting.getSecondaryCpuAbi();
+        info.primaryCpuAbi = pkgSetting.getPrimaryCpuAbi();
+        info.secondaryCpuAbi = pkgSetting.getSecondaryCpuAbi();
         info.nativeLibraryDir = pkg.getNativeLibraryDir();
         info.secondaryNativeLibraryDir = pkg.getSecondaryNativeLibraryDir();
 
@@ -820,12 +781,11 @@
      * all uninstalled and hidden packages as well.
      */
     public static boolean checkUseInstalledOrHidden(AndroidPackage pkg,
-            PackageStateInternal pkgSetting, PackageUserStateInternal state,
+            @NonNull PackageStateInternal pkgSetting, PackageUserStateInternal state,
             @PackageManager.PackageInfoFlagsBits long flags) {
         // Returns false if the package is hidden system app until installed.
         if ((flags & PackageManager.MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS) == 0
                 && !state.isInstalled()
-                && pkgSetting != null
                 && pkgSetting.getTransientState().isHiddenUntilInstalled()) {
             return false;
         }
@@ -878,7 +838,7 @@
 
     private static void assignFieldsComponentInfoParsedMainComponent(
             @NonNull ComponentInfo info, @NonNull ParsedMainComponent component,
-            @Nullable PackageStateInternal pkgSetting, int userId) {
+            @NonNull PackageStateInternal pkgSetting, @UserIdInt int userId) {
         assignFieldsComponentInfoParsedMainComponent(info, component);
         Pair<CharSequence, Integer> labelAndIcon =
                 ParsedComponentStateUtils.getNonLocalizedLabelAndIcon(component, pkgSetting,
@@ -889,7 +849,7 @@
 
     private static void assignFieldsPackageItemInfoParsedComponent(
             @NonNull PackageItemInfo info, @NonNull ParsedComponent component,
-            @Nullable PackageStateInternal pkgSetting, int userId) {
+            @NonNull PackageStateInternal pkgSetting, @UserIdInt int userId) {
         assignFieldsPackageItemInfoParsedComponent(info, component);
         Pair<CharSequence, Integer> labelAndIcon =
                 ParsedComponentStateUtils.getNonLocalizedLabelAndIcon(component, pkgSetting,
@@ -1141,7 +1101,7 @@
         @Nullable
         public ApplicationInfo generate(AndroidPackage pkg,
                 @PackageManager.ApplicationInfoFlagsBits long flags, PackageUserStateInternal state,
-                int userId, @Nullable PackageStateInternal pkgSetting) {
+                int userId, @NonNull PackageStateInternal pkgSetting) {
             ApplicationInfo appInfo = mCache.get(pkg.getPackageName());
             if (appInfo != null) {
                 return appInfo;
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
index a6f1b29..5b0cc51 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
@@ -321,8 +321,4 @@
         info.versionCode = ((ParsingPackageHidden) pkg).getVersionCode();
         info.versionCodeMajor = ((ParsingPackageHidden) pkg).getVersionCodeMajor();
     }
-
-    public static ApplicationInfo toAppInfoWithoutState(AndroidPackage pkg) {
-        return ((ParsingPackageHidden) pkg).toAppInfoWithoutState();
-    }
 }
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index 799ef41..ab223ef 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -4245,7 +4245,6 @@
         }
         boolean changed = false;
 
-        Set<Permission> needsUpdate = null;
         synchronized (mLock) {
             final Iterator<Permission> it = mRegistry.getPermissionTrees().iterator();
             while (it.hasNext()) {
@@ -4264,26 +4263,6 @@
                             + " that used to be declared by " + bp.getPackageName());
                     it.remove();
                 }
-                if (needsUpdate == null) {
-                    needsUpdate = new ArraySet<>();
-                }
-                needsUpdate.add(bp);
-            }
-        }
-        if (needsUpdate != null) {
-            for (final Permission bp : needsUpdate) {
-                final AndroidPackage sourcePkg =
-                        mPackageManagerInt.getPackage(bp.getPackageName());
-                final PackageStateInternal sourcePs =
-                        mPackageManagerInt.getPackageStateInternal(bp.getPackageName());
-                synchronized (mLock) {
-                    if (sourcePkg != null && sourcePs != null) {
-                        continue;
-                    }
-                    Slog.w(TAG, "Removing dangling permission tree: " + bp.getName()
-                            + " from package " + bp.getPackageName());
-                    mRegistry.removePermission(bp.getName());
-                }
             }
         }
         return changed;
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index c4e122d..98b5c1b 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -256,6 +256,7 @@
     static final int SHORT_PRESS_POWER_GO_HOME = 4;
     static final int SHORT_PRESS_POWER_CLOSE_IME_OR_GO_HOME = 5;
     static final int SHORT_PRESS_POWER_LOCK_OR_SLEEP = 6;
+    static final int SHORT_PRESS_POWER_DREAM_OR_SLEEP = 7;
 
     // must match: config_LongPressOnPowerBehavior in config.xml
     static final int LONG_PRESS_POWER_NOTHING = 0;
@@ -969,7 +970,14 @@
             powerMultiPressAction(eventTime, interactive, mTriplePressOnPowerBehavior);
         } else if (count > 3 && count <= getMaxMultiPressPowerCount()) {
             Slog.d(TAG, "No behavior defined for power press count " + count);
-        } else if (count == 1 && interactive && !beganFromNonInteractive) {
+        } else if (count == 1 && interactive) {
+            if (beganFromNonInteractive) {
+                // The "screen is off" case, where we might want to start dreaming on power button
+                // press.
+                attemptToDreamFromShortPowerButtonPress(false, () -> {});
+                return;
+            }
+
             if (mSideFpsEventHandler.shouldConsumeSinglePress(eventTime)) {
                 Slog.i(TAG, "Suppressing power key because the user is interacting with the "
                         + "fingerprint sensor");
@@ -1018,11 +1026,39 @@
                     }
                     break;
                 }
+                case SHORT_PRESS_POWER_DREAM_OR_SLEEP: {
+                    attemptToDreamFromShortPowerButtonPress(
+                            true,
+                            () -> sleepDefaultDisplayFromPowerButton(eventTime, 0));
+                    break;
+                }
             }
         }
     }
 
     /**
+     * Attempt to dream from a power button press.
+     *
+     * @param isScreenOn Whether the screen is currently on.
+     * @param noDreamAction The action to perform if dreaming is not possible.
+     */
+    private void attemptToDreamFromShortPowerButtonPress(
+            boolean isScreenOn, Runnable noDreamAction) {
+        if (mShortPressOnPowerBehavior != SHORT_PRESS_POWER_DREAM_OR_SLEEP) {
+            noDreamAction.run();
+            return;
+        }
+
+        final DreamManagerInternal dreamManagerInternal = getDreamManagerInternal();
+        if (dreamManagerInternal == null || !dreamManagerInternal.canStartDreaming(isScreenOn)) {
+            noDreamAction.run();
+            return;
+        }
+
+        dreamManagerInternal.requestDream();
+    }
+
+    /**
      * Sends the default display to sleep as a result of a power button press.
      *
      * @return {@code true} if the device was sent to sleep, {@code false} if the device did not
@@ -1593,7 +1629,8 @@
 
         // If there's a dream running then use home to escape the dream
         // but don't actually go home.
-        if (mDreamManagerInternal != null && mDreamManagerInternal.isDreaming()) {
+        final DreamManagerInternal dreamManagerInternal = getDreamManagerInternal();
+        if (dreamManagerInternal != null && dreamManagerInternal.isDreaming()) {
             mDreamManagerInternal.stopDream(false /*immediate*/, "short press on home" /*reason*/);
             return;
         }
@@ -2529,6 +2566,15 @@
         }
     }
 
+    private DreamManagerInternal getDreamManagerInternal() {
+        if (mDreamManagerInternal == null) {
+            // If mDreamManagerInternal is null, attempt to re-fetch it.
+            mDreamManagerInternal = LocalServices.getService(DreamManagerInternal.class);
+        }
+
+        return mDreamManagerInternal;
+    }
+
     private void updateWakeGestureListenerLp() {
         if (shouldEnableWakeGestureLp()) {
             mWakeGestureListener.requestWakeUpTrigger();
diff --git a/services/core/java/com/android/server/power/stats/CpuWakeupStats.java b/services/core/java/com/android/server/power/stats/CpuWakeupStats.java
index 5f76fbc..79e35c2 100644
--- a/services/core/java/com/android/server/power/stats/CpuWakeupStats.java
+++ b/services/core/java/com/android/server/power/stats/CpuWakeupStats.java
@@ -384,7 +384,7 @@
     private static final class Wakeup {
         private static final String PARSER_TAG = "CpuWakeupStats.Wakeup";
         private static final String ABORT_REASON_PREFIX = "Abort";
-        private static final Pattern sIrqPattern = Pattern.compile("(\\d+)\\s+(\\S+)");
+        private static final Pattern sIrqPattern = Pattern.compile("^(\\d+)\\s+(\\S+)");
 
         String mRawReason;
         long mElapsedMillis;
@@ -409,7 +409,7 @@
             IrqDevice[] parsedDevices = new IrqDevice[components.length];
 
             for (String component : components) {
-                final Matcher matcher = sIrqPattern.matcher(component);
+                final Matcher matcher = sIrqPattern.matcher(component.trim());
                 if (matcher.find()) {
                     final int line;
                     final String device;
diff --git a/services/core/java/com/android/server/rollback/README.md b/services/core/java/com/android/server/rollback/README.md
index 0c5cc15..08800da 100644
--- a/services/core/java/com/android/server/rollback/README.md
+++ b/services/core/java/com/android/server/rollback/README.md
@@ -1,4 +1,4 @@
-#Rollback Manager
+# Rollback Manager
 
 ## Introduction
 
@@ -7,9 +7,9 @@
 APEX update to the previous version installed on the device, and reverting any
 APK or APEX data to the state it was in at the time of install.
 
-##Rollback Basics
+## Rollback Basics
 
-###How Rollbacks Work
+### How Rollbacks Work
 
 A new install parameter ENABLE_ROLLBACK can be specified to enable rollback when
 updating an application. For example:
@@ -42,27 +42,27 @@
 
 See below for more details of shell commands for rollback.
 
-###Rollback Triggers
+### Rollback Triggers
 
-####Manually Triggered Rollback
+#### Manually Triggered Rollback
 
 As mentioned above, it is possible to trigger rollback on device using a shell
 command. This is for testing purposes only. We do not expect this mechanism to
 be used in production in practice.
 
-####Watchdog Triggered Rollback
+#### Watchdog Triggered Rollback
 
 Watchdog triggered rollback is intended to address severe issues with the
 device. The platform provides several different watchdogs that can trigger
 rollback.
 
-#####Package Watchdog
+##### Package Watchdog
 
 There is a package watchdog service running on device that will trigger rollback
 of an update if there are 5 ANRs or process crashes within a 1 minute window for
 a package in the update.
 
-#####Native Watchdog
+##### Native Watchdog
 
 If a native service crashes repeatedly after an update is installed, rollback
 will be triggered. This particularly applies to updates that include APEXes
@@ -70,25 +70,25 @@
 native services have been affected by an update, *any* crashing native service
 will cause the rollback to be triggered.
 
-#####Explicit Health Check
+##### Explicit Health Check
 
 There is an explicit check to verify the network stack is functional after an
 update. If there is no network connectivity within a certain time period after
 an update, rollback is triggered.
 
-####Server Triggered Rollback
+#### Server Triggered Rollback
 The RollbackManager API may be used by the installer to roll back an update
 based on a request from the server.
 
-##Rollback Details
+## Rollback Details
 
-###RollbackManager API
+### RollbackManager API
 
 The RollbackManager API is an @SystemAPI guarded by the MANAGE_ROLLBACKS and
 TEST_MANAGE_ROLLBACKS permissions. See RollbackManager.java for details about
 the RollbackManager API.
 
-###Rollback of APEX modules
+### Rollback of APEX modules
 
 Rollback is supported for APEX modules in addition to APK modules. In Q, there
 was no concept of data associated with an APEX, so only the APEX itself is
@@ -100,7 +100,7 @@
 directories). For example, FooV2.apex must not change the file format of some
 state stored on the device in such a way that FooV1.apex cannot read the file.
 
-###Rollback of MultiPackage Installs
+### Rollback of MultiPackage Installs
 
 Rollback can be enabled for multi-package installs. This requires that all
 packages in the install session, including the parent session, have the
@@ -119,7 +119,7 @@
 install session, rollback will not be enabled for any package in the
 multi-package install session.
 
-###Rollback of Staged Installs
+### Rollback of Staged Installs
 
 Rollback can be enabled for staged installs, which require reboot to take
 effect. If reboot was required when the package was updated, then reboot is
@@ -127,21 +127,21 @@
 package was updated, then no reboot is required when the package is rolled back.
 
 
-###Rollbacks on Multi User Devices
+### Rollbacks on Multi User Devices
 
 Rollbacks should work properly on devices with multiple users. There is special
 handling of user data backup to ensure app user data is properly backed up and
 restored for all users, even for credential encrypted users that have not been
 unlocked at various points during the flow.
 
-###Rollback whitelist
+### Rollback whitelist
 
 Outside of testing, rollback may only be enabled for packages listed in the
 sysconfig rollback whitelist - see
 `SystemConfig#getRollbackWhitelistedPackages`. Attempts to enable rollback for
 non-whitelisted packages will fail.
 
-###Failure to Enable Rollback
+### Failure to Enable Rollback
 
 There are a number of reasons why we may be unable to enable rollback for a
 package, including:
@@ -158,13 +158,13 @@
 rollback enabled. Failing to enable rollback does not cause the installation to
 fail.
 
-###Failure to Commit Rollback
+### Failure to Commit Rollback
 
 For the most part, a rollback will remain available after failure to commit it.
 This allows the caller to retry the rollback if they have reason to believe it
 will not fail again the next time the commit of the rollback is attempted.
 
-###Installing Previously Rolled Back Packages
+### Installing Previously Rolled Back Packages
 There is no logic in the platform itself to prevent installing a version of a
 package that was previously rolled back.
 
@@ -175,7 +175,7 @@
 installer to prevent reinstall of a previously rolled back package version if so
 desired.
 
-###Rollback Expiration
+### Rollback Expiration
 
 An available rollback is expired if the rollback lifetime has been exceeded or
 if there is a new update to package associated with the rollback. When an
@@ -183,9 +183,9 @@
 the rollback are deleted. Once a rollback is expired, it can no longer be
 executed.
 
-##Shell Commands for Rollback
+## Shell Commands for Rollback
 
-###Installing an App with Rollback Enabled
+### Installing an App with Rollback Enabled
 
 The `adb install` command accepts the `--enable-rollback` flag to install an app
 with rollback enabled. For example:
@@ -194,7 +194,7 @@
 $ adb install --enable-rollback FooV2.apk
 ```
 
-###Triggering Rollback Manually
+### Triggering Rollback Manually
 
 If rollback is available for an application, the pm command can be used to
 trigger rollback manually on device:
@@ -206,7 +206,7 @@
 For rollback of staged installs, you have to manually reboot the device for the
 rollback to take effect after running the 'pm rollback-app' command.
 
-###Listing the Status of Rollbacks on Device
+### Listing the Status of Rollbacks on Device
 
 You can get a list with details about available and recently committed rollbacks
 using dumpsys. For example:
@@ -246,9 +246,9 @@
 The list of rollbacks is also included in bug reports. Search for "DUMP OF
 SERVICE rollback".
 
-##Configuration Properties
+## Configuration Properties
 
-###Rollback Lifetime
+### Rollback Lifetime
 
 Rollback lifetime refers to the maximum duration of time after the rollback is
 first enabled that it will be available. The default is for rollbacks to be
@@ -263,7 +263,7 @@
 
 The update will not take effect until after system server has been restarted.
 
-###Enable Rollback Timeout
+### Enable Rollback Timeout
 
 The enable rollback timeout is how long RollbackManager is allowed to take to
 enable rollback when performing an update. This includes the time needed to make
@@ -279,7 +279,7 @@
 
 The update will take effect for the next install with rollback enabled.
 
-##Limitations
+## Limitations
 
 * You cannot enable rollback for the first version of an application installed
 on the device. Only updates to a package previously installed on the device can
diff --git a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
index 61c21e6..ab35dc8 100644
--- a/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
+++ b/services/core/java/com/android/server/sensorprivacy/SensorPrivacyService.java
@@ -35,6 +35,7 @@
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.hardware.SensorPrivacyManager.EXTRA_ALL_SENSORS;
 import static android.hardware.SensorPrivacyManager.EXTRA_SENSOR;
+import static android.hardware.SensorPrivacyManager.EXTRA_TOGGLE_TYPE;
 import static android.hardware.SensorPrivacyManager.Sensors.CAMERA;
 import static android.hardware.SensorPrivacyManager.Sensors.MICROPHONE;
 import static android.hardware.SensorPrivacyManager.Sources.DIALOG;
@@ -664,6 +665,29 @@
                             .build());
         }
 
+        private void showSensorStateChangedActivity(@SensorPrivacyManager.Sensors.Sensor int sensor,
+                @SensorPrivacyManager.ToggleType int toggleType) {
+            String activityName = mContext.getResources().getString(
+                    R.string.config_sensorStateChangedActivity);
+            if (TextUtils.isEmpty(activityName)) {
+                return;
+            }
+
+            Intent dialogIntent = new Intent();
+            dialogIntent.setComponent(
+                    ComponentName.unflattenFromString(activityName));
+
+            ActivityOptions options = ActivityOptions.makeBasic();
+            options.setTaskOverlay(true, true);
+
+            dialogIntent.addFlags(
+                    FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS | FLAG_ACTIVITY_NO_USER_ACTION);
+
+            dialogIntent.putExtra(EXTRA_SENSOR, sensor);
+            dialogIntent.putExtra(EXTRA_TOGGLE_TYPE, toggleType);
+            mContext.startActivityAsUser(dialogIntent, options.toBundle(), UserHandle.SYSTEM);
+        }
+
         private boolean isTelevision(Context context) {
             int uiMode = context.getResources().getConfiguration().uiMode;
             return (uiMode & Configuration.UI_MODE_TYPE_MASK)
@@ -1378,6 +1402,8 @@
                     mToggleSensorListeners.finishBroadcast();
                 }
             }
+
+            mSensorPrivacyServiceImpl.showSensorStateChangedActivity(sensor, toggleType);
         }
 
         public void removeSuppressPackageReminderToken(Pair<Integer, UserHandle> key,
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
index 4111446..43ffa81 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java
@@ -18,11 +18,11 @@
 
 import android.annotation.Nullable;
 import android.app.ITransientNotificationCallback;
-import android.hardware.fingerprint.IUdfpsHbmListener;
+import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
 import android.os.Bundle;
 import android.os.IBinder;
 import android.view.InsetsState.InternalInsetsType;
-import android.view.InsetsVisibilities;
+import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowInsetsController.Behavior;
 
@@ -158,7 +158,7 @@
     /** @see com.android.internal.statusbar.IStatusBar#onSystemBarAttributesChanged */
     void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
             AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
-            @Behavior int behavior, InsetsVisibilities requestedVisibilities, String packageName,
+            @Behavior int behavior, @InsetsType int requestedVisibleTypes, String packageName,
             LetterboxDetails[] letterboxDetails);
 
     /** @see com.android.internal.statusbar.IStatusBar#showTransient */
@@ -192,9 +192,10 @@
     void setNavigationBarLumaSamplingEnabled(int displayId, boolean enable);
 
     /**
-     * Sets the system-wide listener for UDFPS HBM status changes.
+     * Sets the system-wide callback for UDFPS refresh rate changes.
      *
-     * @see com.android.internal.statusbar.IStatusBar#setUdfpsHbmListener(IUdfpsHbmListener)
+     * @see com.android.internal.statusbar.IStatusBar#setUdfpsRefreshRateCallback
+     * (IUdfpsRefreshRateRequestCallback)
      */
-    void setUdfpsHbmListener(IUdfpsHbmListener listener);
+    void setUdfpsRefreshRateCallback(IUdfpsRefreshRateRequestCallback callback);
 }
diff --git a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
index d378b11..7281a47 100644
--- a/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
+++ b/services/core/java/com/android/server/statusbar/StatusBarManagerService.java
@@ -16,12 +16,15 @@
 
 package com.android.server.statusbar;
 
+import static android.Manifest.permission.INTERACT_ACROSS_USERS;
+import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
 import static android.app.StatusBarManager.DISABLE2_GLOBAL_ACTIONS;
 import static android.app.StatusBarManager.DISABLE2_NOTIFICATION_SHADE;
 import static android.app.StatusBarManager.NAV_BAR_MODE_DEFAULT;
 import static android.app.StatusBarManager.NAV_BAR_MODE_KIDS;
 import static android.app.StatusBarManager.NavBarMode;
 import static android.app.StatusBarManager.SessionFlags;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY;
 
@@ -53,7 +56,7 @@
 import android.hardware.biometrics.PromptInfo;
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManager.DisplayListener;
-import android.hardware.fingerprint.IUdfpsHbmListener;
+import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
 import android.media.INearbyMediaDevicesProvider;
 import android.media.MediaRoute2Info;
 import android.net.Uri;
@@ -80,7 +83,8 @@
 import android.util.Slog;
 import android.util.SparseArray;
 import android.view.InsetsState.InternalInsetsType;
-import android.view.InsetsVisibilities;
+import android.view.WindowInsets;
+import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowInsetsController.Appearance;
 import android.view.WindowInsetsController.Behavior;
 
@@ -172,7 +176,7 @@
 
     private final SparseArray<UiState> mDisplayUiState = new SparseArray<>();
     @GuardedBy("mLock")
-    private IUdfpsHbmListener mUdfpsHbmListener;
+    private IUdfpsRefreshRateRequestCallback mUdfpsRefreshRateRequestCallback;
     @GuardedBy("mLock")
     private IBiometricContextListener mBiometricContextListener;
 
@@ -614,15 +618,15 @@
         @Override
         public void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
                 AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
-                @Behavior int behavior, InsetsVisibilities requestedVisibilities,
+                @Behavior int behavior, @InsetsType int requestedVisibleTypes,
                 String packageName, LetterboxDetails[] letterboxDetails) {
             getUiState(displayId).setBarAttributes(appearance, appearanceRegions,
-                    navbarColorManagedByIme, behavior, requestedVisibilities, packageName,
+                    navbarColorManagedByIme, behavior, requestedVisibleTypes, packageName,
                     letterboxDetails);
             if (mBar != null) {
                 try {
                     mBar.onSystemBarAttributesChanged(displayId, appearance, appearanceRegions,
-                            navbarColorManagedByIme, behavior, requestedVisibilities, packageName,
+                            navbarColorManagedByIme, behavior, requestedVisibleTypes, packageName,
                             letterboxDetails);
                 } catch (RemoteException ex) { }
             }
@@ -691,13 +695,13 @@
         }
 
         @Override
-        public void setUdfpsHbmListener(IUdfpsHbmListener listener) {
+        public void setUdfpsRefreshRateCallback(IUdfpsRefreshRateRequestCallback callback) {
             synchronized (mLock) {
-                mUdfpsHbmListener = listener;
+                mUdfpsRefreshRateRequestCallback = callback;
             }
             if (mBar != null) {
                 try {
-                    mBar.setUdfpsHbmListener(listener);
+                    mBar.setUdfpsRefreshRateCallback(callback);
                 } catch (RemoteException ex) { }
             }
         }
@@ -941,11 +945,11 @@
     }
 
     @Override
-    public void setUdfpsHbmListener(IUdfpsHbmListener listener) {
+    public void setUdfpsRefreshRateCallback(IUdfpsRefreshRateRequestCallback callback) {
         enforceStatusBarService();
         if (mBar != null) {
             try {
-                mBar.setUdfpsHbmListener(listener);
+                mBar.setUdfpsRefreshRateCallback(callback);
             } catch (RemoteException ex) {
             }
         }
@@ -1208,7 +1212,7 @@
         private final ArraySet<Integer> mTransientBarTypes = new ArraySet<>();
         private boolean mNavbarColorManagedByIme = false;
         private @Behavior int mBehavior;
-        private InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
+        private @InsetsType int mRequestedVisibleTypes = WindowInsets.Type.defaultVisible();
         private String mPackageName = "none";
         private int mDisabled1 = 0;
         private int mDisabled2 = 0;
@@ -1220,14 +1224,14 @@
 
         private void setBarAttributes(@Appearance int appearance,
                 AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
-                @Behavior int behavior, InsetsVisibilities requestedVisibilities,
+                @Behavior int behavior, @InsetsType int requestedVisibleTypes,
                 String packageName,
                 LetterboxDetails[] letterboxDetails) {
             mAppearance = appearance;
             mAppearanceRegions = appearanceRegions;
             mNavbarColorManagedByIme = navbarColorManagedByIme;
             mBehavior = behavior;
-            mRequestedVisibilities = requestedVisibilities;
+            mRequestedVisibleTypes = requestedVisibleTypes;
             mPackageName = packageName;
             mLetterboxDetails = letterboxDetails;
         }
@@ -1304,18 +1308,23 @@
                 "StatusBarManagerService");
     }
 
+    private boolean doesCallerHoldInteractAcrossUserPermission() {
+        return mContext.checkCallingPermission(INTERACT_ACROSS_USERS_FULL) == PERMISSION_GRANTED
+                || mContext.checkCallingPermission(INTERACT_ACROSS_USERS) == PERMISSION_GRANTED;
+    }
+
     /**
      *  For targetSdk S+ we require STATUS_BAR. For targetSdk < S, we only require EXPAND_STATUS_BAR
      *  but also require that it falls into one of the allowed use-cases to lock down abuse vector.
      */
     private boolean checkCanCollapseStatusBar(String method) {
         int uid = Binder.getCallingUid();
-        int pid = Binder.getCallingUid();
+        int pid = Binder.getCallingPid();
         if (CompatChanges.isChangeEnabled(LOCK_DOWN_COLLAPSE_STATUS_BAR, uid)) {
             enforceStatusBar();
         } else {
             if (mContext.checkPermission(Manifest.permission.STATUS_BAR, pid, uid)
-                    != PackageManager.PERMISSION_GRANTED) {
+                    != PERMISSION_GRANTED) {
                 enforceExpandStatusBar();
                 if (!mActivityTaskManager.canCloseSystemDialogs(pid, uid)) {
                     Slog.e(TAG, "Permission Denial: Method " + method + "() requires permission "
@@ -1355,7 +1364,7 @@
                     state.mAppearance, state.mAppearanceRegions, state.mImeWindowVis,
                     state.mImeBackDisposition, state.mShowImeSwitcher,
                     gatherDisableActionsLocked(mCurrentUserId, 2), state.mImeToken,
-                    state.mNavbarColorManagedByIme, state.mBehavior, state.mRequestedVisibilities,
+                    state.mNavbarColorManagedByIme, state.mBehavior, state.mRequestedVisibleTypes,
                     state.mPackageName, transientBarTypes, state.mLetterboxDetails);
         }
     }
@@ -1366,11 +1375,11 @@
             mGlobalActionListener.onGlobalActionsAvailableChanged(mBar != null);
         });
         // If StatusBarService dies, system_server doesn't get killed with it, so we need to make
-        // sure the UDFPS listener is refreshed as well. Deferring to the handler just so to avoid
+        // sure the UDFPS callback is refreshed as well. Deferring to the handler just so to avoid
         // making registerStatusBar re-entrant.
         mHandler.post(() -> {
             synchronized (mLock) {
-                setUdfpsHbmListener(mUdfpsHbmListener);
+                setUdfpsRefreshRateCallback(mUdfpsRefreshRateRequestCallback);
                 setBiometicContextListener(mBiometricContextListener);
             }
         });
@@ -2021,6 +2030,11 @@
         }
 
         final int userId = mCurrentUserId;
+        final int callingUserId = UserHandle.getUserId(Binder.getCallingUid());
+        if (mCurrentUserId != callingUserId && !doesCallerHoldInteractAcrossUserPermission()) {
+            throw new SecurityException("Calling user id: " + callingUserId
+                    + ", cannot call on behalf of current user id: " + mCurrentUserId + ".");
+        }
         final long userIdentity = Binder.clearCallingIdentity();
         try {
             Settings.Secure.putIntForUser(mContext.getContentResolver(),
diff --git a/services/core/java/com/android/server/timedetector/EnvironmentImpl.java b/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
index 4972412..8d106f7 100644
--- a/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
+++ b/services/core/java/com/android/server/timedetector/EnvironmentImpl.java
@@ -28,7 +28,7 @@
 import com.android.server.LocalServices;
 import com.android.server.SystemClockTime;
 import com.android.server.SystemClockTime.TimeConfidence;
-import com.android.server.timezonedetector.ConfigurationChangeListener;
+import com.android.server.timezonedetector.StateChangeListener;
 
 import java.io.PrintWriter;
 import java.util.Objects;
@@ -60,10 +60,10 @@
 
     @Override
     public void setConfigurationInternalChangeListener(
-            @NonNull ConfigurationChangeListener listener) {
-        ConfigurationChangeListener configurationChangeListener =
+            @NonNull StateChangeListener listener) {
+        StateChangeListener stateChangeListener =
                 () -> mHandler.post(listener::onChange);
-        mServiceConfigAccessor.addConfigurationInternalChangeListener(configurationChangeListener);
+        mServiceConfigAccessor.addConfigurationInternalChangeListener(stateChangeListener);
     }
 
     @Override
diff --git a/services/core/java/com/android/server/timedetector/ServerFlags.java b/services/core/java/com/android/server/timedetector/ServerFlags.java
index 773b517..e9827ce 100644
--- a/services/core/java/com/android/server/timedetector/ServerFlags.java
+++ b/services/core/java/com/android/server/timedetector/ServerFlags.java
@@ -25,8 +25,8 @@
 import android.util.ArrayMap;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.server.timezonedetector.ConfigurationChangeListener;
 import com.android.server.timezonedetector.ServiceConfigAccessor;
+import com.android.server.timezonedetector.StateChangeListener;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
@@ -185,8 +185,7 @@
      * ensure O(1) lookup performance when working out whether a listener should trigger.
      */
     @GuardedBy("mListeners")
-    private final ArrayMap<ConfigurationChangeListener, HashSet<String>> mListeners =
-            new ArrayMap<>();
+    private final ArrayMap<StateChangeListener, HashSet<String>> mListeners = new ArrayMap<>();
 
     private static final Object SLOCK = new Object();
 
@@ -213,7 +212,7 @@
 
     private void handlePropertiesChanged(@NonNull DeviceConfig.Properties properties) {
         synchronized (mListeners) {
-            for (Map.Entry<ConfigurationChangeListener, HashSet<String>> listenerEntry
+            for (Map.Entry<StateChangeListener, HashSet<String>> listenerEntry
                     : mListeners.entrySet()) {
                 // It's unclear which set of the following two Sets is going to be larger in the
                 // average case: monitoredKeys will be a subset of the set of possible keys, but
@@ -249,7 +248,7 @@
      * <p>Note: Only for use by long-lived objects like other singletons. There is deliberately no
      * associated remove method.
      */
-    public void addListener(@NonNull ConfigurationChangeListener listener,
+    public void addListener(@NonNull StateChangeListener listener,
             @NonNull Set<String> keys) {
         Objects.requireNonNull(listener);
         Objects.requireNonNull(keys);
diff --git a/services/core/java/com/android/server/timedetector/ServiceConfigAccessor.java b/services/core/java/com/android/server/timedetector/ServiceConfigAccessor.java
index a39f64c..ff180eb 100644
--- a/services/core/java/com/android/server/timedetector/ServiceConfigAccessor.java
+++ b/services/core/java/com/android/server/timedetector/ServiceConfigAccessor.java
@@ -19,7 +19,7 @@
 import android.annotation.UserIdInt;
 import android.app.time.TimeConfiguration;
 
-import com.android.server.timezonedetector.ConfigurationChangeListener;
+import com.android.server.timezonedetector.StateChangeListener;
 
 /**
  * An interface that provides access to service configuration for time detection. This hides
@@ -33,18 +33,18 @@
      * Adds a listener that will be invoked when {@link ConfigurationInternal} may have changed.
      * The listener is invoked on the main thread.
      */
-    void addConfigurationInternalChangeListener(@NonNull ConfigurationChangeListener listener);
+    void addConfigurationInternalChangeListener(@NonNull StateChangeListener listener);
 
     /**
      * Removes a listener previously added via {@link
-     * #addConfigurationInternalChangeListener(ConfigurationChangeListener)}.
+     * #addConfigurationInternalChangeListener(StateChangeListener)}.
      */
-    void removeConfigurationInternalChangeListener(@NonNull ConfigurationChangeListener listener);
+    void removeConfigurationInternalChangeListener(@NonNull StateChangeListener listener);
 
     /**
      * Returns a snapshot of the {@link ConfigurationInternal} for the current user. This is only a
      * snapshot so callers must use {@link
-     * #addConfigurationInternalChangeListener(ConfigurationChangeListener)} to be notified when it
+     * #addConfigurationInternalChangeListener(StateChangeListener)} to be notified when it
      * changes.
      */
     @NonNull
diff --git a/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java b/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java
index 71acf35..4ef713c 100644
--- a/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java
+++ b/services/core/java/com/android/server/timedetector/ServiceConfigAccessorImpl.java
@@ -49,7 +49,7 @@
 import com.android.internal.util.Preconditions;
 import com.android.server.LocalServices;
 import com.android.server.timedetector.TimeDetectorStrategy.Origin;
-import com.android.server.timezonedetector.ConfigurationChangeListener;
+import com.android.server.timezonedetector.StateChangeListener;
 
 import java.time.Instant;
 import java.util.ArrayList;
@@ -104,8 +104,8 @@
     @NonNull private final ServerFlagsOriginPrioritiesSupplier mServerFlagsOriginPrioritiesSupplier;
 
     @GuardedBy("this")
-    @NonNull private final List<ConfigurationChangeListener> mConfigurationInternalListeners =
-            new ArrayList<>();
+    @NonNull
+    private final List<StateChangeListener> mConfigurationInternalListeners = new ArrayList<>();
 
     /**
      * If a newly calculated system clock time and the current system clock time differs by this or
@@ -167,20 +167,20 @@
     }
 
     private synchronized void handleConfigurationInternalChangeOnMainThread() {
-        for (ConfigurationChangeListener changeListener : mConfigurationInternalListeners) {
+        for (StateChangeListener changeListener : mConfigurationInternalListeners) {
             changeListener.onChange();
         }
     }
 
     @Override
     public synchronized void addConfigurationInternalChangeListener(
-            @NonNull ConfigurationChangeListener listener) {
+            @NonNull StateChangeListener listener) {
         mConfigurationInternalListeners.add(Objects.requireNonNull(listener));
     }
 
     @Override
     public synchronized void removeConfigurationInternalChangeListener(
-            @NonNull ConfigurationChangeListener listener) {
+            @NonNull StateChangeListener listener) {
         mConfigurationInternalListeners.remove(Objects.requireNonNull(listener));
     }
 
diff --git a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
index 3cee19c..13ec753 100644
--- a/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timedetector/TimeDetectorStrategyImpl.java
@@ -44,8 +44,8 @@
 import com.android.server.SystemClockTime;
 import com.android.server.SystemClockTime.TimeConfidence;
 import com.android.server.timezonedetector.ArrayMapWithHistory;
-import com.android.server.timezonedetector.ConfigurationChangeListener;
 import com.android.server.timezonedetector.ReferenceWithHistory;
+import com.android.server.timezonedetector.StateChangeListener;
 
 import java.io.PrintWriter;
 import java.time.Duration;
@@ -136,11 +136,11 @@
     public interface Environment {
 
         /**
-         * Sets a {@link ConfigurationChangeListener} that will be invoked when there are any
-         * changes that could affect the content of {@link ConfigurationInternal}.
+         * Sets a {@link StateChangeListener} that will be invoked when there are any changes that
+         * could affect the content of {@link ConfigurationInternal}.
          * This is invoked during system server setup.
          */
-        void setConfigurationInternalChangeListener(@NonNull ConfigurationChangeListener listener);
+        void setConfigurationInternalChangeListener(@NonNull StateChangeListener listener);
 
         /** Returns the {@link ConfigurationInternal} for the current user. */
         @NonNull ConfigurationInternal getCurrentUserConfigurationInternal();
diff --git a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
index 8e2a5f4..0409a84 100644
--- a/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
+++ b/services/core/java/com/android/server/timezonedetector/ConfigurationInternal.java
@@ -26,7 +26,6 @@
 import android.annotation.UserIdInt;
 import android.app.time.Capabilities.CapabilityState;
 import android.app.time.TimeZoneCapabilities;
-import android.app.time.TimeZoneCapabilitiesAndConfig;
 import android.app.time.TimeZoneConfiguration;
 import android.os.UserHandle;
 
@@ -75,7 +74,7 @@
         mEnhancedMetricsCollectionEnabled = builder.mEnhancedMetricsCollectionEnabled;
         mAutoDetectionEnabledSetting = builder.mAutoDetectionEnabledSetting;
 
-        mUserId = builder.mUserId;
+        mUserId = Objects.requireNonNull(builder.mUserId, "userId must be set");
         mUserConfigAllowed = builder.mUserConfigAllowed;
         mLocationEnabledSetting = builder.mLocationEnabledSetting;
         mGeoDetectionEnabledSetting = builder.mGeoDetectionEnabledSetting;
@@ -151,8 +150,7 @@
      * Returns true if the user is allowed to modify time zone configuration, e.g. can be false due
      * to device policy (enterprise).
      *
-     * <p>See also {@link #createCapabilitiesAndConfig(boolean)} for situations where this value
-     * are ignored.
+     * <p>See also {@link #asCapabilities(boolean)} for situations where this value is ignored.
      */
     public boolean isUserConfigAllowed() {
         return mUserConfigAllowed;
@@ -196,20 +194,8 @@
                 || getGeoDetectionRunInBackgroundEnabled());
     }
 
-    /**
-     * Creates a {@link TimeZoneCapabilitiesAndConfig} object using the configuration values.
-     *
-     * @param bypassUserPolicyChecks {@code true} for device policy manager use cases where device
-     *   policy restrictions that should apply to actual users can be ignored
-     */
-    public TimeZoneCapabilitiesAndConfig createCapabilitiesAndConfig(
-            boolean bypassUserPolicyChecks) {
-        return new TimeZoneCapabilitiesAndConfig(
-                asCapabilities(bypassUserPolicyChecks), asConfiguration());
-    }
-
     @NonNull
-    private TimeZoneCapabilities asCapabilities(boolean bypassUserPolicyChecks) {
+    public TimeZoneCapabilities asCapabilities(boolean bypassUserPolicyChecks) {
         UserHandle userHandle = UserHandle.of(mUserId);
         TimeZoneCapabilities.Builder builder = new TimeZoneCapabilities.Builder(userHandle);
 
@@ -262,7 +248,7 @@
     }
 
     /** Returns a {@link TimeZoneConfiguration} from the configuration values. */
-    private TimeZoneConfiguration asConfiguration() {
+    public TimeZoneConfiguration asConfiguration() {
         return new TimeZoneConfiguration.Builder()
                 .setAutoDetectionEnabled(getAutoDetectionEnabledSetting())
                 .setGeoDetectionEnabled(getGeoDetectionEnabledSetting())
@@ -335,8 +321,7 @@
      */
     public static class Builder {
 
-        private final @UserIdInt int mUserId;
-
+        private @UserIdInt Integer mUserId;
         private boolean mUserConfigAllowed;
         private boolean mTelephonyDetectionSupported;
         private boolean mGeoDetectionSupported;
@@ -348,11 +333,9 @@
         private boolean mGeoDetectionEnabledSetting;
 
         /**
-         * Creates a new Builder with only the userId set.
+         * Creates a new Builder.
          */
-        public Builder(@UserIdInt int userId) {
-            mUserId = userId;
-        }
+        public Builder() {}
 
         /**
          * Creates a new Builder by copying values from an existing instance.
@@ -371,6 +354,14 @@
         }
 
         /**
+         * Sets the user ID the configuration is for.
+         */
+        public Builder setUserId(@UserIdInt int userId) {
+            mUserId = userId;
+            return this;
+        }
+
+        /**
          * Sets whether the user is allowed to configure time zone settings on this device.
          */
         public Builder setUserConfigAllowed(boolean configAllowed) {
diff --git a/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java b/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java
index 4749f73..5cb48c2 100644
--- a/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/EnvironmentImpl.java
@@ -18,8 +18,6 @@
 
 import android.annotation.ElapsedRealtimeLong;
 import android.annotation.NonNull;
-import android.content.Context;
-import android.os.Handler;
 import android.os.SystemClock;
 import android.os.SystemProperties;
 
@@ -29,7 +27,6 @@
 import com.android.server.SystemTimeZone.TimeZoneConfidence;
 
 import java.io.PrintWriter;
-import java.util.Objects;
 
 /**
  * The real implementation of {@link TimeZoneDetectorStrategyImpl.Environment}.
@@ -38,29 +35,7 @@
 
     private static final String TIMEZONE_PROPERTY = "persist.sys.timezone";
 
-    @NonNull private final Context mContext;
-    @NonNull private final Handler mHandler;
-    @NonNull private final ServiceConfigAccessor mServiceConfigAccessor;
-
-    EnvironmentImpl(@NonNull Context context, @NonNull Handler handler,
-            @NonNull ServiceConfigAccessor serviceConfigAccessor) {
-        mContext = Objects.requireNonNull(context);
-        mHandler = Objects.requireNonNull(handler);
-        mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor);
-    }
-
-    @Override
-    public void setConfigurationInternalChangeListener(
-            @NonNull ConfigurationChangeListener listener) {
-        ConfigurationChangeListener configurationChangeListener =
-                () -> mHandler.post(listener::onChange);
-        mServiceConfigAccessor.addConfigurationInternalChangeListener(configurationChangeListener);
-    }
-
-    @Override
-    @NonNull
-    public ConfigurationInternal getCurrentUserConfigurationInternal() {
-        return mServiceConfigAccessor.getCurrentUserConfigurationInternal();
+    EnvironmentImpl() {
     }
 
     @Override
diff --git a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java
index 8da5d6a..4ac2ba5 100644
--- a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java
+++ b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessor.java
@@ -59,20 +59,18 @@
      * Adds a listener that will be invoked when {@link ConfigurationInternal} may have changed.
      * The listener is invoked on the main thread.
      */
-    void addConfigurationInternalChangeListener(
-            @NonNull ConfigurationChangeListener listener);
+    void addConfigurationInternalChangeListener(@NonNull StateChangeListener listener);
 
     /**
      * Removes a listener previously added via {@link
-     * #addConfigurationInternalChangeListener(ConfigurationChangeListener)}.
+     * #addConfigurationInternalChangeListener(StateChangeListener)}.
      */
-    void removeConfigurationInternalChangeListener(
-            @NonNull ConfigurationChangeListener listener);
+    void removeConfigurationInternalChangeListener(@NonNull StateChangeListener listener);
 
     /**
      * Returns a snapshot of the {@link ConfigurationInternal} for the current user. This is only a
      * snapshot so callers must use {@link
-     * #addConfigurationInternalChangeListener(ConfigurationChangeListener)} to be notified when it
+     * #addConfigurationInternalChangeListener(StateChangeListener)} to be notified when it
      * changes.
      */
     @NonNull
@@ -104,8 +102,7 @@
      *
      * <p>Note: Currently only for use by long-lived objects; there is no associated remove method.
      */
-    void addLocationTimeZoneManagerConfigListener(
-            @NonNull ConfigurationChangeListener listener);
+    void addLocationTimeZoneManagerConfigListener(@NonNull StateChangeListener listener);
 
     /**
      * Returns {@code true} if the telephony-based time zone detection feature is supported on the
diff --git a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java
index e2f4246..dfb9619 100644
--- a/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/ServiceConfigAccessorImpl.java
@@ -22,7 +22,6 @@
 import android.annotation.UserIdInt;
 import android.app.ActivityManagerInternal;
 import android.app.time.TimeZoneCapabilities;
-import android.app.time.TimeZoneCapabilitiesAndConfig;
 import android.app.time.TimeZoneConfiguration;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
@@ -104,8 +103,8 @@
     @NonNull private final LocationManager mLocationManager;
 
     @GuardedBy("this")
-    @NonNull private final List<ConfigurationChangeListener> mConfigurationInternalListeners =
-            new ArrayList<>();
+    @NonNull
+    private final List<StateChangeListener> mConfigurationInternalListeners = new ArrayList<>();
 
     /**
      * The mode to use for the primary location time zone provider in a test. Setting this
@@ -207,20 +206,20 @@
     }
 
     private synchronized void handleConfigurationInternalChangeOnMainThread() {
-        for (ConfigurationChangeListener changeListener : mConfigurationInternalListeners) {
+        for (StateChangeListener changeListener : mConfigurationInternalListeners) {
             changeListener.onChange();
         }
     }
 
     @Override
     public synchronized void addConfigurationInternalChangeListener(
-            @NonNull ConfigurationChangeListener listener) {
+            @NonNull StateChangeListener listener) {
         mConfigurationInternalListeners.add(Objects.requireNonNull(listener));
     }
 
     @Override
     public synchronized void removeConfigurationInternalChangeListener(
-            @NonNull ConfigurationChangeListener listener) {
+            @NonNull StateChangeListener listener) {
         mConfigurationInternalListeners.remove(Objects.requireNonNull(listener));
     }
 
@@ -237,10 +236,10 @@
             @NonNull TimeZoneConfiguration requestedConfiguration, boolean bypassUserPolicyChecks) {
         Objects.requireNonNull(requestedConfiguration);
 
-        TimeZoneCapabilitiesAndConfig capabilitiesAndConfig = getConfigurationInternal(userId)
-                .createCapabilitiesAndConfig(bypassUserPolicyChecks);
-        TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
-        TimeZoneConfiguration oldConfiguration = capabilitiesAndConfig.getConfiguration();
+        ConfigurationInternal configurationInternal = getConfigurationInternal(userId);
+        TimeZoneCapabilities capabilities =
+                configurationInternal.asCapabilities(bypassUserPolicyChecks);
+        TimeZoneConfiguration oldConfiguration = configurationInternal.asConfiguration();
 
         final TimeZoneConfiguration newConfiguration =
                 capabilities.tryApplyConfigChanges(oldConfiguration, requestedConfiguration);
@@ -292,7 +291,8 @@
     @Override
     @NonNull
     public synchronized ConfigurationInternal getConfigurationInternal(@UserIdInt int userId) {
-        return new ConfigurationInternal.Builder(userId)
+        return new ConfigurationInternal.Builder()
+                .setUserId(userId)
                 .setTelephonyDetectionFeatureSupported(
                         isTelephonyTimeZoneDetectionFeatureSupported())
                 .setGeoDetectionFeatureSupported(isGeoTimeZoneDetectionFeatureSupported())
@@ -354,7 +354,7 @@
 
     @Override
     public void addLocationTimeZoneManagerConfigListener(
-            @NonNull ConfigurationChangeListener listener) {
+            @NonNull StateChangeListener listener) {
         mServerFlags.addListener(listener, LOCATION_TIME_ZONE_MANAGER_SERVER_FLAGS_KEYS_TO_WATCH);
     }
 
diff --git a/services/core/java/com/android/server/timezonedetector/ConfigurationChangeListener.java b/services/core/java/com/android/server/timezonedetector/StateChangeListener.java
similarity index 78%
rename from services/core/java/com/android/server/timezonedetector/ConfigurationChangeListener.java
rename to services/core/java/com/android/server/timezonedetector/StateChangeListener.java
index aa8ad37..2b5639c 100644
--- a/services/core/java/com/android/server/timezonedetector/ConfigurationChangeListener.java
+++ b/services/core/java/com/android/server/timezonedetector/StateChangeListener.java
@@ -17,11 +17,11 @@
 package com.android.server.timezonedetector;
 
 /**
- * A listener used to receive notification that configuration has / may have changed (depending on
+ * A listener used to receive notification that state has / may have changed (depending on
  * the usecase).
  */
 @FunctionalInterface
-public interface ConfigurationChangeListener {
-    /** Called when the configuration may have changed. */
+public interface StateChangeListener {
+    /** Called when something (may have) changed. */
     void onChange();
 }
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java
index ce64eac..dfb44df 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorInternalImpl.java
@@ -35,17 +35,14 @@
     @NonNull private final Context mContext;
     @NonNull private final Handler mHandler;
     @NonNull private final CurrentUserIdentityInjector mCurrentUserIdentityInjector;
-    @NonNull private final ServiceConfigAccessor mServiceConfigAccessor;
     @NonNull private final TimeZoneDetectorStrategy mTimeZoneDetectorStrategy;
 
     public TimeZoneDetectorInternalImpl(@NonNull Context context, @NonNull Handler handler,
             @NonNull CurrentUserIdentityInjector currentUserIdentityInjector,
-            @NonNull ServiceConfigAccessor serviceConfigAccessor,
             @NonNull TimeZoneDetectorStrategy timeZoneDetectorStrategy) {
         mContext = Objects.requireNonNull(context);
         mHandler = Objects.requireNonNull(handler);
         mCurrentUserIdentityInjector = Objects.requireNonNull(currentUserIdentityInjector);
-        mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor);
         mTimeZoneDetectorStrategy = Objects.requireNonNull(timeZoneDetectorStrategy);
     }
 
@@ -53,10 +50,9 @@
     @NonNull
     public TimeZoneCapabilitiesAndConfig getCapabilitiesAndConfigForDpm() {
         int currentUserId = mCurrentUserIdentityInjector.getCurrentUserId();
-        ConfigurationInternal configurationInternal =
-                mServiceConfigAccessor.getConfigurationInternal(currentUserId);
         final boolean bypassUserPolicyChecks = true;
-        return configurationInternal.createCapabilitiesAndConfig(bypassUserPolicyChecks);
+        return mTimeZoneDetectorStrategy.getCapabilitiesAndConfig(
+                currentUserId, bypassUserPolicyChecks);
     }
 
     @Override
@@ -65,7 +61,7 @@
 
         int currentUserId = mCurrentUserIdentityInjector.getCurrentUserId();
         final boolean bypassUserPolicyChecks = true;
-        return mServiceConfigAccessor.updateConfiguration(
+        return mTimeZoneDetectorStrategy.updateConfiguration(
                 currentUserId, configuration, bypassUserPolicyChecks);
     }
 
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
index 13f1694..f415cf0 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorService.java
@@ -83,7 +83,7 @@
             ServiceConfigAccessor serviceConfigAccessor =
                     ServiceConfigAccessorImpl.getInstance(context);
             TimeZoneDetectorStrategy timeZoneDetectorStrategy =
-                    TimeZoneDetectorStrategyImpl.create(context, handler, serviceConfigAccessor);
+                    TimeZoneDetectorStrategyImpl.create(handler, serviceConfigAccessor);
             DeviceActivityMonitor deviceActivityMonitor =
                     DeviceActivityMonitorImpl.create(context, handler);
 
@@ -99,16 +99,14 @@
             CurrentUserIdentityInjector currentUserIdentityInjector =
                     CurrentUserIdentityInjector.REAL;
             TimeZoneDetectorInternal internal = new TimeZoneDetectorInternalImpl(
-                    context, handler, currentUserIdentityInjector, serviceConfigAccessor,
-                    timeZoneDetectorStrategy);
+                    context, handler, currentUserIdentityInjector, timeZoneDetectorStrategy);
             publishLocalService(TimeZoneDetectorInternal.class, internal);
 
             // Publish the binder service so it can be accessed from other (appropriately
             // permissioned) processes.
             CallerIdentityInjector callerIdentityInjector = CallerIdentityInjector.REAL;
             TimeZoneDetectorService service = new TimeZoneDetectorService(
-                    context, handler, callerIdentityInjector, serviceConfigAccessor,
-                    timeZoneDetectorStrategy);
+                    context, handler, callerIdentityInjector, timeZoneDetectorStrategy);
 
             // Dump the device activity monitor when the service is dumped.
             service.addDumpable(deviceActivityMonitor);
@@ -127,9 +125,6 @@
     private final CallerIdentityInjector mCallerIdentityInjector;
 
     @NonNull
-    private final ServiceConfigAccessor mServiceConfigAccessor;
-
-    @NonNull
     private final TimeZoneDetectorStrategy mTimeZoneDetectorStrategy;
 
     /**
@@ -150,18 +145,16 @@
     @VisibleForTesting
     public TimeZoneDetectorService(@NonNull Context context, @NonNull Handler handler,
             @NonNull CallerIdentityInjector callerIdentityInjector,
-            @NonNull ServiceConfigAccessor serviceConfigAccessor,
             @NonNull TimeZoneDetectorStrategy timeZoneDetectorStrategy) {
         mContext = Objects.requireNonNull(context);
         mHandler = Objects.requireNonNull(handler);
         mCallerIdentityInjector = Objects.requireNonNull(callerIdentityInjector);
-        mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor);
         mTimeZoneDetectorStrategy = Objects.requireNonNull(timeZoneDetectorStrategy);
 
         // Wire up a change listener so that ITimeZoneDetectorListeners can be notified when
-        // the configuration changes for any reason.
-        mServiceConfigAccessor.addConfigurationInternalChangeListener(
-                () -> mHandler.post(this::handleConfigurationInternalChangedOnHandlerThread));
+        // the detector state changes for any reason.
+        mTimeZoneDetectorStrategy.addChangeListener(
+                () -> mHandler.post(this::handleChangeOnHandlerThread));
     }
 
     @Override
@@ -174,12 +167,15 @@
     TimeZoneCapabilitiesAndConfig getCapabilitiesAndConfig(@UserIdInt int userId) {
         enforceManageTimeZoneDetectorPermission();
 
+        // Resolve constants like USER_CURRENT to the true user ID as needed.
+        int resolvedUserId = ActivityManager.handleIncomingUser(Binder.getCallingPid(),
+                Binder.getCallingUid(), userId, false, false, "getCapabilitiesAndConfig", null);
+
         final long token = mCallerIdentityInjector.clearCallingIdentity();
         try {
-            ConfigurationInternal configurationInternal =
-                    mServiceConfigAccessor.getConfigurationInternal(userId);
             final boolean bypassUserPolicyChecks = false;
-            return configurationInternal.createCapabilitiesAndConfig(bypassUserPolicyChecks);
+            return mTimeZoneDetectorStrategy.getCapabilitiesAndConfig(
+                    resolvedUserId, bypassUserPolicyChecks);
         } finally {
             mCallerIdentityInjector.restoreCallingIdentity(token);
         }
@@ -204,7 +200,7 @@
         final long token = mCallerIdentityInjector.clearCallingIdentity();
         try {
             final boolean bypassUserPolicyChecks = false;
-            return mServiceConfigAccessor.updateConfiguration(
+            return mTimeZoneDetectorStrategy.updateConfiguration(
                     resolvedUserId, configuration, bypassUserPolicyChecks);
         } finally {
             mCallerIdentityInjector.restoreCallingIdentity(token);
@@ -285,8 +281,9 @@
         }
     }
 
-    void handleConfigurationInternalChangedOnHandlerThread() {
-        // Configuration has changed, but each user may have a different view of the configuration.
+    void handleChangeOnHandlerThread() {
+        // Detector state has changed. Each user may have a different view of the configuration so
+        // no information is passed; each client must query what they're interested in.
         // It's possible that this will cause unnecessary notifications but that shouldn't be a
         // problem.
         synchronized (mListeners) {
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
index 69284e3..328cf72 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategy.java
@@ -17,6 +17,8 @@
 
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
+import android.app.time.TimeZoneCapabilitiesAndConfig;
+import android.app.time.TimeZoneConfiguration;
 import android.app.time.TimeZoneState;
 import android.app.timezonedetector.ManualTimeZoneSuggestion;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
@@ -94,6 +96,48 @@
  */
 public interface TimeZoneDetectorStrategy extends Dumpable {
 
+    /**
+     * Adds a listener that will be triggered when something changes that could affect the result
+     * of the {@link #getCapabilitiesAndConfig} call for the <em>current user only</em>. This
+     * includes the current user changing. This is exposed so that (indirect) users like SettingsUI
+     * can monitor for changes to data derived from {@link TimeZoneCapabilitiesAndConfig} and update
+     * the UI accordingly.
+     */
+    void addChangeListener(StateChangeListener listener);
+
+    /**
+     * Returns a {@link TimeZoneCapabilitiesAndConfig} object for the specified user.
+     *
+     * <p>The strategy is dependent on device state like current user, settings and device config.
+     * These updates are usually handled asynchronously, so callers should expect some delay between
+     * a change being made directly to services like settings and the strategy becoming aware of
+     * them. Changes made via {@link #updateConfiguration} will be visible immediately.
+     *
+     * @param userId the user ID to retrieve the information for
+     * @param bypassUserPolicyChecks {@code true} for device policy manager use cases where device
+     *   policy restrictions that should apply to actual users can be ignored
+     */
+    TimeZoneCapabilitiesAndConfig getCapabilitiesAndConfig(
+            @UserIdInt int userId, boolean bypassUserPolicyChecks);
+
+    /**
+     * Updates the configuration properties that control a device's time zone behavior.
+     *
+     * <p>This method returns {@code true} if the configuration was changed, {@code false}
+     * otherwise.
+     *
+     * <p>See {@link #getCapabilitiesAndConfig} for guarantees about visibility of updates to
+     * subsequent calls.
+     *
+     * @param userId the current user ID, supplied to make sure that the asynchronous process
+     *   that happens when users switch is completed when the call is made
+     * @param configuration the configuration changes
+     * @param bypassUserPolicyChecks {@code true} for device policy manager use cases where device
+     *   policy restrictions that should apply to actual users can be ignored
+     */
+    boolean updateConfiguration(@UserIdInt int userId, TimeZoneConfiguration configuration,
+            boolean bypassUserPolicyChecks);
+
     /** Returns a snapshot of the system time zone state. See {@link TimeZoneState} for details. */
     @NonNull
     TimeZoneState getTimeZoneState();
diff --git a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
index 18c8885..ecf25e9 100644
--- a/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/TimeZoneDetectorStrategyImpl.java
@@ -31,10 +31,10 @@
 import android.annotation.UserIdInt;
 import android.app.time.TimeZoneCapabilities;
 import android.app.time.TimeZoneCapabilitiesAndConfig;
+import android.app.time.TimeZoneConfiguration;
 import android.app.time.TimeZoneState;
 import android.app.timezonedetector.ManualTimeZoneSuggestion;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
-import android.content.Context;
 import android.os.Handler;
 import android.os.TimestampedValue;
 import android.util.IndentingPrintWriter;
@@ -46,6 +46,7 @@
 
 import java.io.PrintWriter;
 import java.time.Duration;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
 
@@ -68,16 +69,6 @@
     public interface Environment {
 
         /**
-         * Sets a {@link ConfigurationChangeListener} that will be invoked when there are any
-         * changes that could affect the content of {@link ConfigurationInternal}.
-         * This is invoked during system server setup.
-         */
-        void setConfigurationInternalChangeListener(@NonNull ConfigurationChangeListener listener);
-
-        /** Returns the {@link ConfigurationInternal} for the current user. */
-        @NonNull ConfigurationInternal getCurrentUserConfigurationInternal();
-
-        /**
          * Returns the device's currently configured time zone. May return an empty string.
          */
         @NonNull String getDeviceTimeZone();
@@ -206,6 +197,22 @@
     private final ReferenceWithHistory<ManualTimeZoneSuggestion> mLatestManualSuggestion =
             new ReferenceWithHistory<>(KEEP_SUGGESTION_HISTORY_SIZE);
 
+    @NonNull
+    private final ServiceConfigAccessor mServiceConfigAccessor;
+
+    /** The handler used for asynchronous operations triggered by this. */
+    @NonNull
+    private final Handler mStateChangeHandler;
+
+    @GuardedBy("this")
+    @NonNull private final List<StateChangeListener> mStateChangeListeners = new ArrayList<>();
+
+    /**
+     * A snapshot of the current user's {@link ConfigurationInternal}. A local copy is cached
+     * because it is relatively heavyweight to obtain and is used more often than it is expected to
+     * change. Because many operations are asynchronous, this value may be out of date but should
+     * be "eventually consistent".
+     */
     @GuardedBy("this")
     @NonNull
     private ConfigurationInternal mCurrentConfigurationInternal;
@@ -229,29 +236,93 @@
      * Creates a new instance of {@link TimeZoneDetectorStrategyImpl}.
      */
     public static TimeZoneDetectorStrategyImpl create(
-            @NonNull Context context, @NonNull Handler handler,
-            @NonNull ServiceConfigAccessor serviceConfigAccessor) {
+            @NonNull Handler handler, @NonNull ServiceConfigAccessor serviceConfigAccessor) {
 
-        Environment environment = new EnvironmentImpl(context, handler, serviceConfigAccessor);
-        return new TimeZoneDetectorStrategyImpl(environment);
+        Environment environment = new EnvironmentImpl();
+        return new TimeZoneDetectorStrategyImpl(serviceConfigAccessor, handler, environment);
     }
 
     @VisibleForTesting
-    public TimeZoneDetectorStrategyImpl(@NonNull Environment environment) {
+    public TimeZoneDetectorStrategyImpl(
+            @NonNull ServiceConfigAccessor serviceConfigAccessor,
+            @NonNull Handler handler, @NonNull Environment environment) {
         mEnvironment = Objects.requireNonNull(environment);
+        mServiceConfigAccessor = Objects.requireNonNull(serviceConfigAccessor);
+        mStateChangeHandler = Objects.requireNonNull(handler);
 
         // Start with telephony fallback enabled.
         mTelephonyTimeZoneFallbackEnabled =
                 new TimestampedValue<>(mEnvironment.elapsedRealtimeMillis(), true);
 
         synchronized (this) {
-            mEnvironment.setConfigurationInternalChangeListener(
-                    this::handleConfigurationInternalChanged);
-            mCurrentConfigurationInternal = mEnvironment.getCurrentUserConfigurationInternal();
+            // Listen for config and user changes and get an initial snapshot of configuration.
+            StateChangeListener stateChangeListener = this::handleConfigurationInternalMaybeChanged;
+            mServiceConfigAccessor.addConfigurationInternalChangeListener(stateChangeListener);
+            mCurrentConfigurationInternal =
+                    mServiceConfigAccessor.getCurrentUserConfigurationInternal();
         }
     }
 
     @Override
+    public synchronized TimeZoneCapabilitiesAndConfig getCapabilitiesAndConfig(
+            @UserIdInt int userId, boolean bypassUserPolicyChecks) {
+        ConfigurationInternal configurationInternal;
+        if (mCurrentConfigurationInternal.getUserId() == userId) {
+            // Use the cached snapshot we have.
+            configurationInternal = mCurrentConfigurationInternal;
+        } else {
+            // This is not a common case: It would be unusual to want the configuration for a user
+            // other than the "current" user, but it is supported because it is trivial to do so.
+            // Unlike the current user config, there's no cached copy to worry about so read it
+            // directly from mServiceConfigAccessor.
+            configurationInternal = mServiceConfigAccessor.getConfigurationInternal(userId);
+        }
+        return new TimeZoneCapabilitiesAndConfig(
+                configurationInternal.asCapabilities(bypassUserPolicyChecks),
+                configurationInternal.asConfiguration());
+    }
+
+    @Override
+    public synchronized boolean updateConfiguration(
+            @UserIdInt int userId, @NonNull TimeZoneConfiguration configuration,
+            boolean bypassUserPolicyChecks) {
+
+        // Write-through
+        boolean updateSuccessful = mServiceConfigAccessor.updateConfiguration(
+                userId, configuration, bypassUserPolicyChecks);
+
+        // The update above will trigger config update listeners asynchronously if they are needed,
+        // but that could mean an immediate call to getCapabilitiesAndConfig() for the current user
+        // wouldn't see the update. So, handle the cache update and notifications here. When the
+        // async update listener triggers it will find everything already up to date and do nothing.
+        if (updateSuccessful && mCurrentConfigurationInternal.getUserId() == userId) {
+            ConfigurationInternal configurationInternal =
+                    mServiceConfigAccessor.getConfigurationInternal(userId);
+
+            // If the configuration actually changed, update the cached copy synchronously and do
+            // other necessary house-keeping / (async) listener notifications.
+            if (!configurationInternal.equals(mCurrentConfigurationInternal)) {
+                mCurrentConfigurationInternal = configurationInternal;
+
+                String logMsg = "updateConfiguration:"
+                        + " userId=" + userId
+                        + ", configuration=" + configuration
+                        + ", bypassUserPolicyChecks=" + bypassUserPolicyChecks
+                        + ", mCurrentConfigurationInternal=" + mCurrentConfigurationInternal;
+                logTimeZoneDebugInfo(logMsg);
+
+                handleConfigurationInternalChanged(logMsg);
+            }
+        }
+        return updateSuccessful;
+    }
+
+    @Override
+    public synchronized void addChangeListener(StateChangeListener listener) {
+        mStateChangeListeners.add(listener);
+    }
+
+    @Override
     public synchronized boolean confirmTimeZone(@NonNull String timeZoneId) {
         Objects.requireNonNull(timeZoneId);
 
@@ -334,9 +405,8 @@
         String timeZoneId = suggestion.getZoneId();
         String cause = "Manual time suggestion received: suggestion=" + suggestion;
 
-        TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
-                currentUserConfig.createCapabilitiesAndConfig(bypassUserPolicyChecks);
-        TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+        TimeZoneCapabilities capabilities =
+                currentUserConfig.asCapabilities(bypassUserPolicyChecks);
         if (capabilities.getSetManualTimeZoneCapability() != CAPABILITY_POSSESSED) {
             Slog.i(LOG_TAG, "User does not have the capability needed to set the time zone manually"
                     + ": capabilities=" + capabilities
@@ -735,18 +805,37 @@
         return findBestTelephonySuggestion();
     }
 
-    private synchronized void handleConfigurationInternalChanged() {
+    /**
+     * Handles a configuration change notification.
+     */
+    private synchronized void handleConfigurationInternalMaybeChanged() {
         ConfigurationInternal currentUserConfig =
-                mEnvironment.getCurrentUserConfigurationInternal();
-        String logMsg = "handleConfigurationInternalChanged:"
-                + " oldConfiguration=" + mCurrentConfigurationInternal
-                + ", newConfiguration=" + currentUserConfig;
-        logTimeZoneDebugInfo(logMsg);
-        mCurrentConfigurationInternal = currentUserConfig;
+                mServiceConfigAccessor.getCurrentUserConfigurationInternal();
 
-        // The configuration change may have changed available suggestions or the way suggestions
-        // are used, so re-run detection.
-        doAutoTimeZoneDetection(currentUserConfig, logMsg);
+        // The configuration may not actually have changed so check before doing anything.
+        if (!currentUserConfig.equals(mCurrentConfigurationInternal)) {
+            String logMsg = "handleConfigurationInternalMaybeChanged:"
+                    + " oldConfiguration=" + mCurrentConfigurationInternal
+                    + ", newConfiguration=" + currentUserConfig;
+            logTimeZoneDebugInfo(logMsg);
+
+            mCurrentConfigurationInternal = currentUserConfig;
+
+            handleConfigurationInternalChanged(logMsg);
+        }
+    }
+
+    /** House-keeping that needs to be done when the mCurrentConfigurationInternal has changed. */
+    @GuardedBy("this")
+    private void handleConfigurationInternalChanged(@NonNull String logMsg) {
+        // Notify change listeners asynchronously.
+        for (StateChangeListener listener : mStateChangeListeners) {
+            mStateChangeHandler.post(listener::onChange);
+        }
+
+        // The configuration change may have changed available suggestions or the way
+        // suggestions are used, so re-run detection.
+        doAutoTimeZoneDetection(mCurrentConfigurationInternal, logMsg);
     }
 
     /**
@@ -760,8 +849,7 @@
         ipw.println("mCurrentConfigurationInternal=" + mCurrentConfigurationInternal);
         final boolean bypassUserPolicyChecks = false;
         ipw.println("[Capabilities="
-                + mCurrentConfigurationInternal.createCapabilitiesAndConfig(bypassUserPolicyChecks)
-                + "]");
+                + mCurrentConfigurationInternal.asCapabilities(bypassUserPolicyChecks) + "]");
         ipw.println("mEnvironment.getDeviceTimeZone()=" + mEnvironment.getDeviceTimeZone());
         ipw.println("mEnvironment.getDeviceTimeZoneConfidence()="
                 + mEnvironment.getDeviceTimeZoneConfidence());
@@ -824,6 +912,11 @@
         return mTelephonyTimeZoneFallbackEnabled.getValue();
     }
 
+    @VisibleForTesting
+    public synchronized ConfigurationInternal getCachedCapabilitiesAndConfigForTests() {
+        return mCurrentConfigurationInternal;
+    }
+
     /**
      * A {@link TelephonyTimeZoneSuggestion} with additional qualifying metadata.
      */
diff --git a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerEnvironmentImpl.java b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerEnvironmentImpl.java
index e7d16c8..5eeafc1 100644
--- a/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerEnvironmentImpl.java
+++ b/services/core/java/com/android/server/timezonedetector/location/LocationTimeZoneProviderControllerEnvironmentImpl.java
@@ -20,9 +20,9 @@
 import android.annotation.NonNull;
 import android.os.SystemClock;
 
-import com.android.server.timezonedetector.ConfigurationChangeListener;
 import com.android.server.timezonedetector.ConfigurationInternal;
 import com.android.server.timezonedetector.ServiceConfigAccessor;
+import com.android.server.timezonedetector.StateChangeListener;
 
 import java.time.Duration;
 import java.util.Objects;
@@ -35,7 +35,7 @@
         extends LocationTimeZoneProviderController.Environment {
 
     @NonNull private final ServiceConfigAccessor mServiceConfigAccessor;
-    @NonNull private final ConfigurationChangeListener mConfigurationInternalChangeListener;
+    @NonNull private final StateChangeListener mConfigurationInternalChangeListener;
 
     LocationTimeZoneProviderControllerEnvironmentImpl(@NonNull ThreadingDomain threadingDomain,
             @NonNull ServiceConfigAccessor serviceConfigAccessor,
diff --git a/services/core/java/com/android/server/utils/EventLogger.java b/services/core/java/com/android/server/utils/EventLogger.java
index 004312f..770cb72 100644
--- a/services/core/java/com/android/server/utils/EventLogger.java
+++ b/services/core/java/com/android/server/utils/EventLogger.java
@@ -26,7 +26,9 @@
 import java.lang.annotation.RetentionPolicy;
 import java.text.SimpleDateFormat;
 import java.util.ArrayDeque;
+import java.util.ArrayList;
 import java.util.Date;
+import java.util.List;
 import java.util.Locale;
 
 /**
@@ -43,7 +45,7 @@
     /**
      * The maximum number of events to keep in {@code mEvents}.
      *
-     * <p>Calling {@link #log} when the size of {@link #mEvents} matches the threshold will
+     * <p>Calling {@link #enqueue} when the size of {@link #mEvents} matches the threshold will
      * cause the oldest event to be evicted.
      */
     private final int mMemSize;
@@ -60,7 +62,7 @@
     }
 
     /** Enqueues {@code event} to be logged. */
-    public synchronized void log(Event event) {
+    public synchronized void enqueue(Event event) {
         if (mEvents.size() >= mMemSize) {
             mEvents.removeLast();
         }
@@ -69,24 +71,19 @@
     }
 
     /**
-     * Add a string-based event to the log, and print it to logcat as info.
-     * @param msg the message for the logs
-     * @param tag the logcat tag to use
-     */
-    public synchronized void loglogi(String msg, String tag) {
-        final Event event = new StringEvent(msg);
-        log(event.printLog(tag));
-    }
-
-    /**
-     * Same as {@link #loglogi(String, String)} but specifying the logcat type
+     * Add a string-based event to the log, and print it to logcat with a specific severity.
      * @param msg the message for the logs
      * @param logType the type of logcat entry
      * @param tag the logcat tag to use
      */
-    public synchronized void loglog(String msg, @Event.LogType int logType, String tag) {
+    public synchronized void enqueueAndLog(String msg, @Event.LogType int logType, String tag) {
         final Event event = new StringEvent(msg);
-        log(event.printLog(logType, tag));
+        enqueue(event.printLog(logType, tag));
+    }
+
+    /** Dumps events into the given {@link DumpSink}. */
+    public synchronized void dump(DumpSink dumpSink) {
+        dumpSink.sink(mTag, new ArrayList<>(mEvents));
     }
 
     /** Dumps events using {@link PrintWriter}. */
@@ -95,14 +92,23 @@
     }
 
     /** Dumps events using {@link PrintWriter} with a certain indent. */
-    public synchronized void dump(PrintWriter pw, String prefix) {
-        pw.println(prefix + "Events log: " + mTag);
-        String indent = prefix + "  ";
+    public synchronized void dump(PrintWriter pw, String indent) {
+        pw.println(indent + "Events log: " + mTag);
+
+        String childrenIndention = indent + "  ";
         for (Event evt : mEvents) {
-            pw.println(indent + evt.toString());
+            pw.println(childrenIndention + evt.toString());
         }
     }
 
+    /** Receives events from {@link EventLogger} upon a {@link #dump(DumpSink)} call. **/
+    public interface DumpSink {
+
+        /** Processes given events into some pipeline with a given tag. **/
+        void sink(String tag, List<Event> events);
+
+    }
+
     public abstract static class Event {
 
         /** Timestamps formatter. */
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index abb57bc..7dc4f97 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -320,9 +320,7 @@
                                 IRemoteCallback.Stub callback = new IRemoteCallback.Stub() {
                                     @Override
                                     public void sendResult(Bundle data) throws RemoteException {
-                                        if (DEBUG) {
-                                            Slog.d(TAG, "publish system wallpaper changed!");
-                                        }
+                                        Slog.d(TAG, "publish system wallpaper changed!");
                                         notifyWallpaperChanged(wallpaper);
                                     }
                                 };
@@ -1551,6 +1549,8 @@
                     try {
                         mReply.sendResult(null);
                     } catch (RemoteException e) {
+                        Slog.d(TAG, "failed to send callback!", e);
+                    } finally {
                         Binder.restoreCallingIdentity(ident);
                     }
                     mReply = null;
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 17a9a63..0eee8a3 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -496,7 +496,7 @@
     /** The most recently given options. */
     private ActivityOptions mPendingOptions;
     /** Non-null if {@link #mPendingOptions} specifies the remote animation. */
-    private RemoteAnimationAdapter mPendingRemoteAnimation;
+    RemoteAnimationAdapter mPendingRemoteAnimation;
     private RemoteTransition mPendingRemoteTransition;
     ActivityOptions returningOptions; // options that are coming back via convertToTranslucent
     AppTimeTracker appTimeTracker; // set if we are tracking the time in this app/task/activity
@@ -812,7 +812,6 @@
     StartingData mStartingData;
     WindowState mStartingWindow;
     StartingSurfaceController.StartingSurface mStartingSurface;
-    boolean startingDisplayed;
     boolean startingMoved;
 
     /** The last set {@link DropInputMode} for this activity surface. */
@@ -821,13 +820,6 @@
     /** Whether the input to this activity will be dropped during the current playing animation. */
     private boolean mIsInputDroppedForAnimation;
 
-    /**
-     * If it is non-null, it requires all activities who have the same starting data to be drawn
-     * to remove the starting window.
-     * TODO(b/189385912): Remove starting window related fields after migrating them to task.
-     */
-    private StartingData mSharedStartingData;
-
     boolean mHandleExitSplashScreen;
     @TransferSplashScreenState
     int mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_IDLE;
@@ -1201,14 +1193,11 @@
             pw.print(" firstWindowDrawn="); pw.print(firstWindowDrawn);
             pw.print(" mIsExiting="); pw.println(mIsExiting);
         }
-        if (mSharedStartingData != null) {
-            pw.println(prefix + "mSharedStartingData=" + mSharedStartingData);
-        }
-        if (mStartingWindow != null || mStartingSurface != null
-                || startingDisplayed || startingMoved || mVisibleSetFromTransferredStartingWindow) {
+        if (mStartingWindow != null || mStartingData != null || mStartingSurface != null
+                || startingMoved || mVisibleSetFromTransferredStartingWindow) {
             pw.print(prefix); pw.print("startingWindow="); pw.print(mStartingWindow);
             pw.print(" startingSurface="); pw.print(mStartingSurface);
-            pw.print(" startingDisplayed="); pw.print(startingDisplayed);
+            pw.print(" startingDisplayed="); pw.print(isStartingWindowDisplayed());
             pw.print(" startingMoved="); pw.print(startingMoved);
             pw.println(" mVisibleSetFromTransferredStartingWindow="
                     + mVisibleSetFromTransferredStartingWindow);
@@ -2687,13 +2676,23 @@
         }
     }
 
+    boolean isStartingWindowDisplayed() {
+        final StartingData data = mStartingData != null ? mStartingData : task != null
+                ? task.mSharedStartingData : null;
+        return data != null && data.mIsDisplayed;
+    }
+
     /** Called when the starting window is added to this activity. */
     void attachStartingWindow(@NonNull WindowState startingWindow) {
         startingWindow.mStartingData = mStartingData;
         mStartingWindow = startingWindow;
-        // The snapshot type may have called associateStartingDataWithTask().
-        if (mStartingData != null && mStartingData.mAssociatedTask != null) {
-            attachStartingSurfaceToAssociatedTask();
+        if (mStartingData != null) {
+            if (mStartingData.mAssociatedTask != null) {
+                // The snapshot type may have called associateStartingDataWithTask().
+                attachStartingSurfaceToAssociatedTask();
+            } else if (isEmbedded()) {
+                associateStartingWindowWithTaskIfNeeded();
+            }
         }
     }
 
@@ -2708,11 +2707,7 @@
     /** Called when the starting window is not added yet but its data is known to fill the task. */
     private void associateStartingDataWithTask() {
         mStartingData.mAssociatedTask = task;
-        task.forAllActivities(r -> {
-            if (r.mVisibleRequested && !r.firstWindowDrawn) {
-                r.mSharedStartingData = mStartingData;
-            }
-        });
+        task.mSharedStartingData = mStartingData;
     }
 
     /** Associates and attaches an added starting window to the current task. */
@@ -2743,10 +2738,8 @@
 
     void removeStartingWindowAnimation(boolean prepareAnimation) {
         mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_IDLE;
-        if (mSharedStartingData != null) {
-            mSharedStartingData.mAssociatedTask.forAllActivities(r -> {
-                r.mSharedStartingData = null;
-            });
+        if (task != null) {
+            task.mSharedStartingData = null;
         }
         if (mStartingWindow == null) {
             if (mStartingData != null) {
@@ -2774,7 +2767,6 @@
             mStartingData = null;
             mStartingSurface = null;
             mStartingWindow = null;
-            startingDisplayed = false;
             if (surface == null) {
                 ProtoLog.v(WM_DEBUG_STARTING_WINDOW, "startingWindow was set but "
                         + "startingSurface==null, couldn't remove");
@@ -4247,7 +4239,7 @@
      * @return {@code true} if starting window is in app's hierarchy.
      */
     boolean hasStartingWindow() {
-        if (startingDisplayed || mStartingData != null) {
+        if (mStartingData != null) {
             return true;
         }
         for (int i = mChildren.size() - 1; i >= 0; i--) {
@@ -4345,10 +4337,7 @@
 
                 // Transfer the starting window over to the new token.
                 mStartingData = fromActivity.mStartingData;
-                mSharedStartingData = fromActivity.mSharedStartingData;
                 mStartingSurface = fromActivity.mStartingSurface;
-                startingDisplayed = fromActivity.startingDisplayed;
-                fromActivity.startingDisplayed = false;
                 mStartingWindow = tStartingWindow;
                 reportedVisible = fromActivity.reportedVisible;
                 fromActivity.mStartingData = null;
@@ -4414,7 +4403,6 @@
             ProtoLog.v(WM_DEBUG_STARTING_WINDOW,
                     "Moving pending starting from %s to %s", fromActivity, this);
             mStartingData = fromActivity.mStartingData;
-            mSharedStartingData = fromActivity.mSharedStartingData;
             fromActivity.mStartingData = null;
             fromActivity.startingMoved = true;
             scheduleAddStartingWindow();
@@ -6526,14 +6514,11 @@
         // Remove starting window directly if is in a pure task. Otherwise if it is associated with
         // a task (e.g. nested task fragment), then remove only if all visible windows in the task
         // are drawn.
-        final Task associatedTask =
-                mSharedStartingData != null ? mSharedStartingData.mAssociatedTask : null;
+        final Task associatedTask = task.mSharedStartingData != null ? task : null;
         if (associatedTask == null) {
             removeStartingWindow();
-        } else if (associatedTask.getActivity(r -> r.mVisibleRequested && !r.firstWindowDrawn
-                // Don't block starting window removal if an Activity can't be a starting window
-                // target.
-                && r.mSharedStartingData != null) == null) {
+        } else if (associatedTask.getActivity(
+                r -> r.mVisibleRequested && !r.firstWindowDrawn) == null) {
             // The last drawn activity may not be the one that owns the starting window.
             final ActivityRecord r = associatedTask.topActivityContainsStartingWindow();
             if (r != null) {
@@ -6748,7 +6733,6 @@
         if (mLastTransactionSequence != mWmService.mTransactionSequence) {
             mLastTransactionSequence = mWmService.mTransactionSequence;
             mNumDrawnWindows = 0;
-            startingDisplayed = false;
 
             // There is the main base application window, even if it is exiting, wait for it
             mNumInterestingWindows = findMainWindow(false /* includeStartingApp */) != null ? 1 : 0;
@@ -6792,9 +6776,9 @@
                         isInterestingAndDrawn = true;
                     }
                 }
-            } else if (w.isDrawn()) {
+            } else if (mStartingData != null && w.isDrawn()) {
                 // The starting window for this container is drawn.
-                startingDisplayed = true;
+                mStartingData.mIsDisplayed = true;
             }
         }
 
@@ -7552,7 +7536,8 @@
 
         ProtoLog.v(WM_DEBUG_ANIM, "Animation done in %s"
                 + ": reportedVisible=%b okToDisplay=%b okToAnimate=%b startingDisplayed=%b",
-                this, reportedVisible, okToDisplay(), okToAnimate(), startingDisplayed);
+                this, reportedVisible, okToDisplay(), okToAnimate(),
+                isStartingWindowDisplayed());
 
         // clean up thumbnail window
         if (mThumbnail != null) {
@@ -9650,7 +9635,7 @@
         if (mStartingWindow != null) {
             mStartingWindow.writeIdentifierToProto(proto, STARTING_WINDOW);
         }
-        proto.write(STARTING_DISPLAYED, startingDisplayed);
+        proto.write(STARTING_DISPLAYED, isStartingWindowDisplayed());
         proto.write(STARTING_MOVED, startingMoved);
         proto.write(VISIBLE_SET_FROM_TRANSFERRED_STARTING_WINDOW,
                 mVisibleSetFromTransferredStartingWindow);
diff --git a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
index 7d84bdf..d7c5e93 100644
--- a/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
+++ b/services/core/java/com/android/server/wm/ActivityStartInterceptor.java
@@ -424,7 +424,7 @@
         try {
             harmfulAppWarning = mService.getPackageManager()
                     .getHarmfulAppWarning(mAInfo.packageName, mUserId);
-        } catch (RemoteException ex) {
+        } catch (RemoteException | IllegalArgumentException ex) {
             return false;
         }
 
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 1d70146..f070064 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -2643,10 +2643,14 @@
             }
         }
 
-        // Update the target's launch cookie to those specified in the options if set
+        // Update the target's launch cookie and pending remote animation to those specified in the
+        // options if set.
         if (mStartActivity.mLaunchCookie != null) {
             intentActivity.mLaunchCookie = mStartActivity.mLaunchCookie;
         }
+        if (mStartActivity.mPendingRemoteAnimation != null) {
+            intentActivity.mPendingRemoteAnimation = mStartActivity.mPendingRemoteAnimation;
+        }
 
         // Need to update mTargetRootTask because if task was moved out of it, the original root
         // task may be destroyed.
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index ecc43f7..b153a85 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -1316,7 +1316,7 @@
                 mAppSwitchesState = APP_SWITCH_ALLOW;
             }
         }
-        return pir.sendInner(0, fillInIntent, resolvedType, allowlistToken, null, null,
+        return pir.sendInner(caller, 0, fillInIntent, resolvedType, allowlistToken, null, null,
                 resultTo, resultWho, requestCode, flagsMask, flagsValues, bOptions);
     }
 
diff --git a/services/core/java/com/android/server/wm/AnrController.java b/services/core/java/com/android/server/wm/AnrController.java
index d42a74f..7c0d658 100644
--- a/services/core/java/com/android/server/wm/AnrController.java
+++ b/services/core/java/com/android/server/wm/AnrController.java
@@ -40,6 +40,7 @@
 import java.io.File;
 import java.util.ArrayList;
 import java.util.OptionalInt;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -332,9 +333,10 @@
             String criticalEvents =
                     CriticalEventLog.getInstance().logLinesForSystemServerTraceFile();
             final File tracesFile = ActivityManagerService.dumpStackTraces(firstPids,
-                    null /* processCpuTracker */, null /* lastPids */, nativePids,
+                    null /* processCpuTracker */, null /* lastPids */,
+                    CompletableFuture.completedFuture(nativePids),
                     null /* logExceptionCreatingFile */, "Pre-dump", criticalEvents,
-                    null/* AnrLatencyTracker */);
+                    Runnable::run, null/* AnrLatencyTracker */);
             if (tracesFile != null) {
                 tracesFile.renameTo(
                         new File(tracesFile.getParent(), tracesFile.getName() + "_pre"));
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index c2e87e6..92d19b0 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -294,8 +294,8 @@
             final int flags = appTransition.getTransitFlags();
             layoutRedo = appTransition.goodToGo(transit, topOpeningApp);
             appTransition.postAnimationCallback();
-            appTransition.clear();
         } finally {
+            appTransition.clear();
             mService.mSurfaceAnimationRunner.continueStartingAnimations();
         }
 
@@ -1203,11 +1203,11 @@
             ProtoLog.v(WM_DEBUG_APP_TRANSITIONS,
                     "Check opening app=%s: allDrawn=%b startingDisplayed=%b "
                             + "startingMoved=%b isRelaunching()=%b startingWindow=%s",
-                    activity, activity.allDrawn, activity.startingDisplayed,
+                    activity, activity.allDrawn, activity.isStartingWindowDisplayed(),
                     activity.startingMoved, activity.isRelaunching(),
                     activity.mStartingWindow);
             final boolean allDrawn = activity.allDrawn && !activity.isRelaunching();
-            if (!allDrawn && !activity.startingDisplayed && !activity.startingMoved) {
+            if (!allDrawn && !activity.isStartingWindowDisplayed() && !activity.startingMoved) {
                 return false;
             }
             if (allDrawn) {
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 1cb83f12..d1d39af 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -243,16 +243,22 @@
             } else if (currentActivity.isRootOfTask()) {
                 // TODO(208789724): Create single source of truth for this, maybe in
                 //  RootWindowContainer
-                // TODO: Also check Task.shouldUpRecreateTaskLocked() for prevActivity logic
                 prevTask = currentTask.mRootWindowContainer.getTaskBelow(currentTask);
                 removedWindowContainer = currentTask;
-                prevActivity = prevTask.getTopNonFinishingActivity();
-                if (prevTask.isActivityTypeHome()) {
-                    backType = BackNavigationInfo.TYPE_RETURN_TO_HOME;
+                // If it reaches the top activity, we will check the below task from parent.
+                // If it's null or multi-window, fallback the type to TYPE_CALLBACK.
+                // or set the type to proper value when it's return to home or another task.
+                if (prevTask == null || prevTask.inMultiWindowMode()) {
+                    backType = BackNavigationInfo.TYPE_CALLBACK;
                 } else {
-                    backType = BackNavigationInfo.TYPE_CROSS_TASK;
+                    prevActivity = prevTask.getTopNonFinishingActivity();
+                    if (prevTask.isActivityTypeHome()) {
+                        backType = BackNavigationInfo.TYPE_RETURN_TO_HOME;
+                        mShowWallpaper = true;
+                    } else {
+                        backType = BackNavigationInfo.TYPE_CROSS_TASK;
+                    }
                 }
-                mShowWallpaper = true;
             }
             infoBuilder.setType(backType);
 
@@ -263,8 +269,10 @@
                     removedWindowContainer,
                     BackNavigationInfo.typeToString(backType));
 
-            // For now, we only animate when going home.
-            boolean prepareAnimation = backType == BackNavigationInfo.TYPE_RETURN_TO_HOME
+            // For now, we only animate when going home and cross task.
+            boolean prepareAnimation =
+                    (backType == BackNavigationInfo.TYPE_RETURN_TO_HOME
+                            || backType == BackNavigationInfo.TYPE_CROSS_TASK)
                     && adapter != null;
 
             // Only prepare animation if no leash has been created (no animation is running).
diff --git a/services/core/java/com/android/server/wm/Dimmer.java b/services/core/java/com/android/server/wm/Dimmer.java
index e7ab63e..13a1cb6 100644
--- a/services/core/java/com/android/server/wm/Dimmer.java
+++ b/services/core/java/com/android/server/wm/Dimmer.java
@@ -216,14 +216,10 @@
             return;
         }
 
-        if (container != null) {
-            // The dim method is called from WindowState.prepareSurfaces(), which is always called
-            // in the correct Z from lowest Z to highest. This ensures that the dim layer is always
-            // relative to the highest Z layer with a dim.
-            t.setRelativeLayer(d.mDimLayer, container.getSurfaceControl(), relativeLayer);
-        } else {
-            t.setLayer(d.mDimLayer, Integer.MAX_VALUE);
-        }
+        // The dim method is called from WindowState.prepareSurfaces(), which is always called
+        // in the correct Z from lowest Z to highest. This ensures that the dim layer is always
+        // relative to the highest Z layer with a dim.
+        t.setRelativeLayer(d.mDimLayer, container.getSurfaceControl(), relativeLayer);
         t.setAlpha(d.mDimLayer, alpha);
         t.setBackgroundBlurRadius(d.mDimLayer, blurRadius);
 
@@ -231,32 +227,6 @@
     }
 
     /**
-     * Finish a dim started by dimAbove in the case there was no call to dimAbove.
-     *
-     * @param t A Transaction in which to finish the dim.
-     */
-    void stopDim(SurfaceControl.Transaction t) {
-        if (mDimState != null) {
-            t.hide(mDimState.mDimLayer);
-            mDimState.isVisible = false;
-            mDimState.mDontReset = false;
-        }
-    }
-
-    /**
-     * Place a Dim above the entire host container. The caller is responsible for calling stopDim to
-     * remove this effect. If the Dim can be assosciated with a particular child of the host
-     * consider using the other variant of dimAbove which ties the Dim lifetime to the child
-     * lifetime more explicitly.
-     *
-     * @param t     A transaction in which to apply the Dim.
-     * @param alpha The alpha at which to Dim.
-     */
-    void dimAbove(SurfaceControl.Transaction t, float alpha) {
-        dim(t, null, 1, alpha, 0);
-    }
-
-    /**
      * Place a dim above the given container, which should be a child of the host container.
      * for each call to {@link WindowContainer#prepareSurfaces} the Dim state will be reset
      * and the child should call dimAbove again to request the Dim to continue.
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 8723994..7bcea36 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -75,11 +75,7 @@
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_SCREEN_ON;
 import static com.android.server.policy.PhoneWindowManager.TOAST_WINDOW_TIMEOUT;
-import static com.android.server.policy.WindowManagerPolicy.TRANSIT_ENTER;
-import static com.android.server.policy.WindowManagerPolicy.TRANSIT_EXIT;
-import static com.android.server.policy.WindowManagerPolicy.TRANSIT_HIDE;
 import static com.android.server.policy.WindowManagerPolicy.TRANSIT_PREVIEW_DONE;
-import static com.android.server.policy.WindowManagerPolicy.TRANSIT_SHOW;
 import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.LID_ABSENT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_LAYOUT;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
@@ -119,11 +115,9 @@
 import android.view.InsetsSource;
 import android.view.InsetsState;
 import android.view.InsetsState.InternalInsetsType;
-import android.view.InsetsVisibilities;
 import android.view.Surface;
 import android.view.View;
 import android.view.ViewDebug;
-import android.view.WindowInsets;
 import android.view.WindowInsets.Type;
 import android.view.WindowInsets.Type.InsetsType;
 import android.view.WindowLayout;
@@ -318,7 +312,6 @@
     private int mLastAppearance;
     private int mLastBehavior;
     private int mLastRequestedVisibleTypes = Type.defaultVisible();
-    private InsetsVisibilities mRequestedVisibilities = new InsetsVisibilities();
     private AppearanceRegion[] mLastStatusBarAppearanceRegions;
     private LetterboxDetails[] mLastLetterboxDetails;
 
@@ -1341,90 +1334,6 @@
      */
     int selectAnimation(WindowState win, int transit) {
         ProtoLog.i(WM_DEBUG_ANIM, "selectAnimation in %s: transit=%d", win, transit);
-        if (win == mStatusBar) {
-            if (transit == TRANSIT_EXIT
-                    || transit == TRANSIT_HIDE) {
-                return R.anim.dock_top_exit;
-            } else if (transit == TRANSIT_ENTER
-                    || transit == TRANSIT_SHOW) {
-                return R.anim.dock_top_enter;
-            }
-        } else if (win == mNavigationBar) {
-            if (win.getAttrs().windowAnimations != 0) {
-                return ANIMATION_STYLEABLE;
-            }
-            // This can be on either the bottom or the right or the left.
-            if (mNavigationBarPosition == NAV_BAR_BOTTOM) {
-                if (transit == TRANSIT_EXIT
-                        || transit == TRANSIT_HIDE) {
-                    if (mService.mPolicy.isKeyguardShowingAndNotOccluded()) {
-                        return R.anim.dock_bottom_exit_keyguard;
-                    } else {
-                        return R.anim.dock_bottom_exit;
-                    }
-                } else if (transit == TRANSIT_ENTER
-                        || transit == TRANSIT_SHOW) {
-                    return R.anim.dock_bottom_enter;
-                }
-            } else if (mNavigationBarPosition == NAV_BAR_RIGHT) {
-                if (transit == TRANSIT_EXIT
-                        || transit == TRANSIT_HIDE) {
-                    return R.anim.dock_right_exit;
-                } else if (transit == TRANSIT_ENTER
-                        || transit == TRANSIT_SHOW) {
-                    return R.anim.dock_right_enter;
-                }
-            } else if (mNavigationBarPosition == NAV_BAR_LEFT) {
-                if (transit == TRANSIT_EXIT
-                        || transit == TRANSIT_HIDE) {
-                    return R.anim.dock_left_exit;
-                } else if (transit == TRANSIT_ENTER
-                        || transit == TRANSIT_SHOW) {
-                    return R.anim.dock_left_enter;
-                }
-            }
-        } else if (win == mStatusBarAlt || win == mNavigationBarAlt || win == mClimateBarAlt
-                || win == mExtraNavBarAlt) {
-            if (win.getAttrs().windowAnimations != 0) {
-                return ANIMATION_STYLEABLE;
-            }
-
-            int pos = (win == mStatusBarAlt) ? mStatusBarAltPosition : mNavigationBarAltPosition;
-
-            boolean isExitOrHide = transit == TRANSIT_EXIT || transit == TRANSIT_HIDE;
-            boolean isEnterOrShow = transit == TRANSIT_ENTER || transit == TRANSIT_SHOW;
-
-            switch (pos) {
-                case ALT_BAR_LEFT:
-                    if (isExitOrHide) {
-                        return R.anim.dock_left_exit;
-                    } else if (isEnterOrShow) {
-                        return R.anim.dock_left_enter;
-                    }
-                    break;
-                case ALT_BAR_RIGHT:
-                    if (isExitOrHide) {
-                        return R.anim.dock_right_exit;
-                    } else if (isEnterOrShow) {
-                        return R.anim.dock_right_enter;
-                    }
-                    break;
-                case ALT_BAR_BOTTOM:
-                    if (isExitOrHide) {
-                        return R.anim.dock_bottom_exit;
-                    } else if (isEnterOrShow) {
-                        return R.anim.dock_bottom_enter;
-                    }
-                    break;
-                case ALT_BAR_TOP:
-                    if (isExitOrHide) {
-                        return R.anim.dock_top_exit;
-                    } else if (isEnterOrShow) {
-                        return R.anim.dock_top_enter;
-                    }
-                    break;
-            }
-        }
 
         if (transit == TRANSIT_PREVIEW_DONE) {
             if (win.hasAppShownWindows()) {
@@ -2246,35 +2155,16 @@
             mService.mInputManager.setSystemUiLightsOut(
                     isFullscreen || (appearance & APPEARANCE_LOW_PROFILE_BARS) != 0);
         }
-        final InsetsVisibilities requestedVisibilities =
-                mLastRequestedVisibleTypes == requestedVisibleTypes
-                        ? mRequestedVisibilities
-                        : toInsetsVisibilities(requestedVisibleTypes);
         mLastAppearance = appearance;
         mLastBehavior = behavior;
         mLastRequestedVisibleTypes = requestedVisibleTypes;
-        mRequestedVisibilities = requestedVisibilities;
         mFocusedApp = focusedApp;
         mLastFocusIsFullscreen = isFullscreen;
         mLastStatusBarAppearanceRegions = statusBarAppearanceRegions;
         mLastLetterboxDetails = letterboxDetails;
         callStatusBarSafely(statusBar -> statusBar.onSystemBarAttributesChanged(displayId,
                 appearance, statusBarAppearanceRegions, isNavbarColorManagedByIme, behavior,
-                requestedVisibilities, focusedApp, letterboxDetails));
-    }
-
-    // TODO (253420890): Remove this when removing mRequestedVisibilities.
-    private static InsetsVisibilities toInsetsVisibilities(@InsetsType int requestedVisibleTypes) {
-        final @InsetsType int defaultVisibleTypes = WindowInsets.Type.defaultVisible();
-        final InsetsVisibilities insetsVisibilities = new InsetsVisibilities();
-        for (@InternalInsetsType int i = InsetsState.SIZE - 1; i >= 0; i--) {
-            @InsetsType int type = InsetsState.toPublicType(i);
-            if ((type & (requestedVisibleTypes ^ defaultVisibleTypes)) != 0) {
-                // We only set the visibility if it is different from the default one.
-                insetsVisibilities.setVisibility(i, (type & requestedVisibleTypes) != 0);
-            }
-        }
-        return insetsVisibilities;
+                requestedVisibleTypes, focusedApp, letterboxDetails));
     }
 
     private void callStatusBarSafely(Consumer<StatusBarManagerInternal> consumer) {
diff --git a/services/core/java/com/android/server/wm/DisplayRotation.java b/services/core/java/com/android/server/wm/DisplayRotation.java
index a8d13c5..eaa08fd 100644
--- a/services/core/java/com/android/server/wm/DisplayRotation.java
+++ b/services/core/java/com/android/server/wm/DisplayRotation.java
@@ -1578,7 +1578,9 @@
                         false /* forceRelayout */);
             } else {
                 // Revert the rotation to our saved value if we transition from HALF_FOLDED.
-                mRotation = mHalfFoldSavedRotation;
+                if (mHalfFoldSavedRotation != -1) {
+                    mRotation = mHalfFoldSavedRotation;
+                }
                 // Tell the device to update its orientation (mFoldState is still HALF_FOLDED here
                 // so we will override USER_ROTATION_LOCKED and allow a rotation).
                 mService.updateRotation(false /* alwaysSendConfiguration */,
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index b9fa80c..7e56dbf 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -26,7 +26,6 @@
 import static android.view.InsetsController.ANIMATION_TYPE_SHOW;
 import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_HIDDEN;
 import static android.view.InsetsController.LAYOUT_INSETS_DURING_ANIMATION_SHOWN;
-import static android.view.InsetsState.ITYPE_CAPTION_BAR;
 import static android.view.InsetsState.ITYPE_CLIMATE_BAR;
 import static android.view.InsetsState.ITYPE_EXTRA_NAVIGATION_BAR;
 import static android.view.InsetsState.ITYPE_IME;
@@ -289,9 +288,8 @@
         final boolean alwaysOnTop = token != null && token.isAlwaysOnTop();
         // Always use windowing mode fullscreen when get insets for window metrics to make sure it
         // contains all insets types.
-        final InsetsState originalState = mDisplayContent.getInsetsPolicy()
-                .enforceInsetsPolicyForTarget(type, WINDOWING_MODE_FULLSCREEN, alwaysOnTop,
-                        attrs.type, mStateController.getRawInsetsState());
+        final InsetsState originalState = enforceInsetsPolicyForTarget(WINDOWING_MODE_FULLSCREEN,
+                alwaysOnTop, attrs, mStateController.getRawInsetsState());
         InsetsState state = adjustVisibilityForTransientTypes(originalState);
         return adjustInsetsForRoundedCorners(token, state, state == originalState);
     }
@@ -343,56 +341,42 @@
 
 
     /**
-     * Modifies the given {@code state} according to the {@code type} (Inset type) provided by
-     * the target.
-     * When performing layout of the target or dispatching insets to the target, we need to exclude
-     * sources which should not be visible to the target. e.g., the source which represents the
-     * target window itself, and the IME source when the target is above IME. We also need to
-     * exclude certain types of insets source for client within specific windowing modes.
+     * Modifies the given {@code state} according to the target's window state.
+     * When performing layout of the target or dispatching insets to the target, we need to adjust
+     * sources based on the target. e.g., the floating window will not receive system bars other
+     * than caption, and some insets provider may request to override sizes for given window types.
+     * Since the window type and the insets types provided by the window shall not change at
+     * runtime, rotation doesn't matter in the layout params.
      *
-     * @param type the inset type provided by the target
      * @param windowingMode the windowing mode of the target
      * @param isAlwaysOnTop is the target always on top
-     * @param windowType the type of the target
+     * @param attrs the layout params of the target
      * @param state the input inset state containing all the sources
      * @return The state stripped of the necessary information.
      */
-    InsetsState enforceInsetsPolicyForTarget(@InternalInsetsType int type,
-            @WindowConfiguration.WindowingMode int windowingMode, boolean isAlwaysOnTop,
-            int windowType, InsetsState state) {
+    InsetsState enforceInsetsPolicyForTarget(@WindowConfiguration.WindowingMode int windowingMode,
+            boolean isAlwaysOnTop, WindowManager.LayoutParams attrs, InsetsState state) {
         boolean stateCopied = false;
 
-        if (type != ITYPE_INVALID) {
+        if (attrs.providedInsets != null && attrs.providedInsets.length > 0) {
             state = new InsetsState(state);
             stateCopied = true;
-            state.removeSource(type);
-
-            // Navigation bar doesn't get influenced by anything else
-            if (type == ITYPE_NAVIGATION_BAR || type == ITYPE_EXTRA_NAVIGATION_BAR) {
-                state.removeSource(ITYPE_STATUS_BAR);
-                state.removeSource(ITYPE_CLIMATE_BAR);
-                state.removeSource(ITYPE_CAPTION_BAR);
-                state.removeSource(ITYPE_NAVIGATION_BAR);
-                state.removeSource(ITYPE_EXTRA_NAVIGATION_BAR);
-            }
-
-            // Status bar doesn't get influenced by caption bar
-            if (type == ITYPE_STATUS_BAR || type == ITYPE_CLIMATE_BAR) {
-                state.removeSource(ITYPE_CAPTION_BAR);
+            for (int i = attrs.providedInsets.length - 1; i >= 0; i--) {
+                state.removeSource(attrs.providedInsets[i].type);
             }
         }
         ArrayMap<Integer, WindowContainerInsetsSourceProvider> providers = mStateController
                 .getSourceProviders();
         for (int i = providers.size() - 1; i >= 0; i--) {
             WindowContainerInsetsSourceProvider otherProvider = providers.valueAt(i);
-            if (otherProvider.overridesFrame(windowType)) {
+            if (otherProvider.overridesFrame(attrs.type)) {
                 if (!stateCopied) {
                     state = new InsetsState(state);
                     stateCopied = true;
                 }
                 InsetsSource override =
                         new InsetsSource(state.getSource(otherProvider.getSource().getType()));
-                override.setFrame(otherProvider.getOverriddenFrame(windowType));
+                override.setFrame(otherProvider.getOverriddenFrame(attrs.type));
                 state.addSource(override);
             }
         }
diff --git a/services/core/java/com/android/server/wm/LockTaskController.java b/services/core/java/com/android/server/wm/LockTaskController.java
index f11c2a7..dcb7fe3 100644
--- a/services/core/java/com/android/server/wm/LockTaskController.java
+++ b/services/core/java/com/android/server/wm/LockTaskController.java
@@ -604,7 +604,10 @@
                 getDevicePolicyManager().notifyLockTaskModeChanged(false, null, userId);
             }
             if (oldLockTaskModeState == LOCK_TASK_MODE_PINNED) {
-                getStatusBarService().showPinningEnterExitToast(false /* entering */);
+                final IStatusBarService statusBarService = getStatusBarService();
+                if (statusBarService != null) {
+                    statusBarService.showPinningEnterExitToast(false /* entering */);
+                }
             }
             mWindowManager.onLockTaskStateChanged(mLockTaskModeState);
         } catch (RemoteException ex) {
@@ -619,7 +622,10 @@
     void showLockTaskToast() {
         if (mLockTaskModeState == LOCK_TASK_MODE_PINNED) {
             try {
-                getStatusBarService().showPinningEscapeToast();
+                final IStatusBarService statusBarService = getStatusBarService();
+                if (statusBarService != null) {
+                    statusBarService.showPinningEscapeToast();
+                }
             } catch (RemoteException e) {
                 Slog.e(TAG, "Failed to send pinning escape toast", e);
             }
@@ -727,7 +733,10 @@
         // When lock task starts, we disable the status bars.
         try {
             if (lockTaskModeState == LOCK_TASK_MODE_PINNED) {
-                getStatusBarService().showPinningEnterExitToast(true /* entering */);
+                final IStatusBarService statusBarService = getStatusBarService();
+                if (statusBarService != null) {
+                    statusBarService.showPinningEnterExitToast(true /* entering */);
+                }
             }
             mWindowManager.onLockTaskStateChanged(lockTaskModeState);
             mLockTaskModeState = lockTaskModeState;
diff --git a/services/core/java/com/android/server/wm/RunningTasks.java b/services/core/java/com/android/server/wm/RunningTasks.java
index 9c85bc0..1cc1a57 100644
--- a/services/core/java/com/android/server/wm/RunningTasks.java
+++ b/services/core/java/com/android/server/wm/RunningTasks.java
@@ -20,13 +20,12 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 
 import android.app.ActivityManager.RunningTaskInfo;
+import android.os.SystemClock;
 import android.os.UserHandle;
 import android.util.ArraySet;
 
-import java.util.Comparator;
-import java.util.Iterator;
+import java.util.ArrayList;
 import java.util.List;
-import java.util.TreeSet;
 import java.util.function.Consumer;
 
 /**
@@ -39,15 +38,13 @@
     static final int FLAG_CROSS_USERS = 1 << 2;
     static final int FLAG_KEEP_INTENT_EXTRA = 1 << 3;
 
-    // Comparator to sort by last active time (descending)
-    private static final Comparator<Task> LAST_ACTIVE_TIME_COMPARATOR =
-            (o1, o2) -> {
-                return o1.lastActiveTime == o2.lastActiveTime
-                        ? Integer.signum(o2.mTaskId - o1.mTaskId) :
-                        Long.signum(o2.lastActiveTime - o1.lastActiveTime);
-            };
-
-    private final TreeSet<Task> mTmpSortedSet = new TreeSet<>(LAST_ACTIVE_TIME_COMPARATOR);
+    // Tasks are sorted in order {focusedVisibleTasks, visibleTasks, invisibleTasks}.
+    private final ArrayList<Task> mTmpSortedTasks = new ArrayList<>();
+    // mTmpVisibleTasks, mTmpInvisibleTasks and mTmpFocusedTasks are sorted from top
+    // to bottom.
+    private final ArrayList<Task> mTmpVisibleTasks = new ArrayList<>();
+    private final ArrayList<Task> mTmpInvisibleTasks = new ArrayList<>();
+    private final ArrayList<Task> mTmpFocusedTasks = new ArrayList<>();
 
     private int mCallingUid;
     private int mUserId;
@@ -65,8 +62,6 @@
             return;
         }
 
-        // Gather all of the tasks across all of the tasks, and add them to the sorted set
-        mTmpSortedSet.clear();
         mCallingUid = callingUid;
         mUserId = UserHandle.getUserId(callingUid);
         mCrossUser = (flags & FLAG_CROSS_USERS) == FLAG_CROSS_USERS;
@@ -77,19 +72,64 @@
         mRecentTasks = recentTasks;
         mKeepIntentExtra = (flags & FLAG_KEEP_INTENT_EXTRA) == FLAG_KEEP_INTENT_EXTRA;
 
-        root.forAllLeafTasks(this, false /* traverseTopToBottom */);
+        if (root instanceof RootWindowContainer) {
+            ((RootWindowContainer) root).forAllDisplays(dc -> {
+                final Task focusedTask = dc.mFocusedApp != null ? dc.mFocusedApp.getTask() : null;
+                if (focusedTask != null) {
+                    mTmpFocusedTasks.add(focusedTask);
+                }
+                processTaskInWindowContainer(dc);
+            });
+        } else {
+            final DisplayContent dc = root.getDisplayContent();
+            final Task focusedTask = dc != null
+                    ? (dc.mFocusedApp != null ? dc.mFocusedApp.getTask() : null)
+                    : null;
+            // May not be include focusedTask if root is DisplayArea.
+            final boolean rootContainsFocusedTask = focusedTask != null
+                    && focusedTask.isDescendantOf(root);
+            if (rootContainsFocusedTask) {
+                mTmpFocusedTasks.add(focusedTask);
+            }
+            processTaskInWindowContainer(root);
+        }
+
+        final int visibleTaskCount = mTmpVisibleTasks.size();
+        for (int i = 0; i < mTmpFocusedTasks.size(); i++) {
+            final Task focusedTask = mTmpFocusedTasks.get(i);
+            final boolean containsFocusedTask = mTmpVisibleTasks.remove(focusedTask);
+            if (containsFocusedTask) {
+                // Put the visible focused task at the first position.
+                mTmpSortedTasks.add(focusedTask);
+            }
+        }
+        if (!mTmpVisibleTasks.isEmpty()) {
+            mTmpSortedTasks.addAll(mTmpVisibleTasks);
+        }
+        if (!mTmpInvisibleTasks.isEmpty()) {
+            mTmpSortedTasks.addAll(mTmpInvisibleTasks);
+        }
 
         // Take the first {@param maxNum} tasks and create running task infos for them
-        final Iterator<Task> iter = mTmpSortedSet.iterator();
-        while (iter.hasNext()) {
-            if (maxNum == 0) {
-                break;
-            }
-
-            final Task task = iter.next();
-            list.add(createRunningTaskInfo(task));
-            maxNum--;
+        final int size = Math.min(maxNum, mTmpSortedTasks.size());
+        final long now = SystemClock.elapsedRealtime();
+        for (int i = 0; i < size; i++) {
+            final Task task = mTmpSortedTasks.get(i);
+            // Override the last active to current time for the visible tasks because the visible
+            // tasks can be considered to be currently active, the values are descending as
+            // the item order.
+            final long visibleActiveTime = i < visibleTaskCount ? now + size - i : -1;
+            list.add(createRunningTaskInfo(task, visibleActiveTime));
         }
+
+        mTmpFocusedTasks.clear();
+        mTmpVisibleTasks.clear();
+        mTmpInvisibleTasks.clear();
+        mTmpSortedTasks.clear();
+    }
+
+    private void processTaskInWindowContainer(WindowContainer wc) {
+        wc.forAllLeafTasks(this, true /* traverseTopToBottom */);
     }
 
     @Override
@@ -117,25 +157,20 @@
             // home & recent tasks
             return;
         }
-
         if (task.isVisible()) {
-            // For the visible task, update the last active time so that it can be used to determine
-            // the order of the tasks (it may not be set for newly created tasks)
-            task.touchActiveTime();
-            if (!task.isFocused()) {
-                // TreeSet doesn't allow the same value and make sure this task is lower than the
-                // focused one.
-                task.lastActiveTime -= mTmpSortedSet.size();
-            }
+            mTmpVisibleTasks.add(task);
+        } else {
+            mTmpInvisibleTasks.add(task);
         }
-
-        mTmpSortedSet.add(task);
     }
 
     /** Constructs a {@link RunningTaskInfo} from a given {@param task}. */
-    private RunningTaskInfo createRunningTaskInfo(Task task) {
+    private RunningTaskInfo createRunningTaskInfo(Task task, long visibleActiveTime) {
         final RunningTaskInfo rti = new RunningTaskInfo();
         task.fillTaskInfo(rti, !mKeepIntentExtra);
+        if (visibleActiveTime > 0) {
+            rti.lastActiveTime = visibleActiveTime;
+        }
         // Fill in some deprecated values
         rti.id = rti.taskId;
 
diff --git a/services/core/java/com/android/server/wm/StartingData.java b/services/core/java/com/android/server/wm/StartingData.java
index fbee343..300a894 100644
--- a/services/core/java/com/android/server/wm/StartingData.java
+++ b/services/core/java/com/android/server/wm/StartingData.java
@@ -38,6 +38,9 @@
      */
     Task mAssociatedTask;
 
+    /** Whether the starting window is drawn. */
+    boolean mIsDisplayed;
+
     protected StartingData(WindowManagerService service, int typeParams) {
         mService = service;
         mTypeParams = typeParams;
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 435ab97..d06f271 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -358,6 +358,13 @@
 
     int mLockTaskUid = -1;  // The uid of the application that called startLockTask().
 
+    /**
+     * If non-null, the starting window should cover the associated task. It is assigned when the
+     * parent activity of starting window is put in a partial area of the task. This field will be
+     * cleared when all visible activities in this task are drawn.
+     */
+    StartingData mSharedStartingData;
+
     /** The process that had previously hosted the root activity of this task.
      * Used to know that we should try harder to keep this process around, in case the
      * user wants to return to it. */
@@ -3673,6 +3680,9 @@
         if (mRootProcess != null) {
             pw.print(prefix); pw.print("mRootProcess="); pw.println(mRootProcess);
         }
+        if (mSharedStartingData != null) {
+            pw.println(prefix + "mSharedStartingData=" + mSharedStartingData);
+        }
         pw.print(prefix); pw.print("taskId=" + mTaskId);
         pw.println(" rootTaskId=" + getRootTaskId());
         pw.print(prefix); pw.println("hasChildPipActivity=" + (mChildPipActivity != null));
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 230b760..063f0db 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1891,10 +1891,10 @@
     RemoteAnimationTarget createRemoteAnimationTarget(
             RemoteAnimationController.RemoteAnimationRecord record) {
         final ActivityRecord activity = record.getMode() == RemoteAnimationTarget.MODE_OPENING
-                // There may be a trampoline activity without window on top of the existing task
-                // which is moving to front. Exclude the finishing activity so the window of next
-                // activity can be chosen to create the animation target.
-                ? getTopNonFinishingActivity()
+                // There may be a launching (e.g. trampoline or embedded) activity without a window
+                // on top of the existing task which is moving to front. Exclude finishing activity
+                // so the window of next activity can be chosen to create the animation target.
+                ? getActivity(r -> !r.finishing && r.hasChild())
                 : getTopMostActivity();
         return activity != null ? activity.createRemoteAnimationTarget(record) : null;
     }
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 80c9803..a64bd69 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -469,6 +469,48 @@
     }
 
     /**
+     * Records that a particular container has been reparented. This only effects windows that have
+     * already been collected in the transition. This should be called before reparenting because
+     * the old parent may be removed during reparenting, for example:
+     * {@link Task#shouldRemoveSelfOnLastChildRemoval}
+     */
+    void collectReparentChange(@NonNull WindowContainer wc, @NonNull WindowContainer newParent) {
+        if (!mChanges.containsKey(wc)) {
+            // #collectReparentChange() will be called when the window is reparented. Skip if it is
+            // a window that has not been collected, which means we don't care about this window for
+            // the current transition.
+            return;
+        }
+        final ChangeInfo change = mChanges.get(wc);
+        // Use the current common ancestor if there are multiple reparent, and the original parent
+        // has been detached. Otherwise, use the original parent before the transition.
+        final WindowContainer prevParent =
+                change.mStartParent == null || change.mStartParent.isAttached()
+                        ? change.mStartParent
+                        : change.mCommonAncestor;
+        if (prevParent == null || !prevParent.isAttached()) {
+            Slog.w(TAG, "Trying to collect reparenting of a window after the previous parent has"
+                    + " been detached: " + wc);
+            return;
+        }
+        if (prevParent == newParent) {
+            Slog.w(TAG, "Trying to collect reparenting of a window that has not been reparented: "
+                    + wc);
+            return;
+        }
+        if (!newParent.isAttached()) {
+            Slog.w(TAG, "Trying to collect reparenting of a window that is not attached after"
+                    + " reparenting: " + wc);
+            return;
+        }
+        WindowContainer ancestor = newParent;
+        while (prevParent != ancestor && !prevParent.isDescendantOf(ancestor)) {
+            ancestor = ancestor.getParent();
+        }
+        change.mCommonAncestor = ancestor;
+    }
+
+    /**
      * @return {@code true} if `wc` is a participant or is a descendant of one.
      */
     boolean isInTransition(WindowContainer wc) {
@@ -830,8 +872,8 @@
     void abort() {
         // This calls back into itself via controller.abort, so just early return here.
         if (mState == STATE_ABORT) return;
-        if (mState != STATE_COLLECTING) {
-            throw new IllegalStateException("Too late to abort.");
+        if (mState != STATE_COLLECTING && mState != STATE_STARTED) {
+            throw new IllegalStateException("Too late to abort. state=" + mState);
         }
         ProtoLog.v(ProtoLogGroup.WM_DEBUG_WINDOW_TRANSITIONS, "Aborting Transition: %d", mSyncId);
         mState = STATE_ABORT;
@@ -1524,20 +1566,7 @@
             return out;
         }
 
-        // Find the top-most shared ancestor of app targets.
-        WindowContainer<?> ancestor = topApp.getParent();
-        // Go up ancestor parent chain until all targets are descendants.
-        ancestorLoop:
-        while (ancestor != null) {
-            for (int i = sortedTargets.size() - 1; i >= 0; --i) {
-                final WindowContainer wc = sortedTargets.get(i);
-                if (!isWallpaper(wc) && !wc.isDescendantOf(ancestor)) {
-                    ancestor = ancestor.getParent();
-                    continue ancestorLoop;
-                }
-            }
-            break;
-        }
+        WindowContainer<?> ancestor = findCommonAncestor(sortedTargets, changes, topApp);
 
         // make leash based on highest (z-order) direct child of ancestor with a participant.
         WindowContainer leashReference = sortedTargets.get(0);
@@ -1654,6 +1683,46 @@
         return out;
     }
 
+    /**
+     * Finds the top-most common ancestor of app targets.
+     *
+     * Makes sure that the previous parent is also a descendant to make sure the animation won't
+     * be covered by other windows below the previous parent. For example, when reparenting an
+     * activity from PiP Task to split screen Task.
+     */
+    @NonNull
+    private static WindowContainer<?> findCommonAncestor(
+            @NonNull ArrayList<WindowContainer> targets,
+            @NonNull ArrayMap<WindowContainer, ChangeInfo> changes,
+            @NonNull WindowContainer<?> topApp) {
+        WindowContainer<?> ancestor = topApp.getParent();
+        // Go up ancestor parent chain until all targets are descendants. Ancestor should never be
+        // null because all targets are attached.
+        for (int i = targets.size() - 1; i >= 0; i--) {
+            final WindowContainer wc = targets.get(i);
+            if (isWallpaper(wc)) {
+                // Skip the non-app window.
+                continue;
+            }
+            while (!wc.isDescendantOf(ancestor)) {
+                ancestor = ancestor.getParent();
+            }
+
+            // Make sure the previous parent is also a descendant to make sure the animation won't
+            // be covered by other windows below the previous parent. For example, when reparenting
+            // an activity from PiP Task to split screen Task.
+            final ChangeInfo change = changes.get(wc);
+            final WindowContainer prevParent = change.mCommonAncestor;
+            if (prevParent == null || !prevParent.isAttached()) {
+                continue;
+            }
+            while (prevParent != ancestor && !prevParent.isDescendantOf(ancestor)) {
+                ancestor = ancestor.getParent();
+            }
+        }
+        return ancestor;
+    }
+
     private static WindowManager.LayoutParams getLayoutParamsForAnimationsStyle(int type,
             ArrayList<WindowContainer> sortedTargets) {
         // Find the layout params of the top-most application window that is part of the
@@ -1772,10 +1841,19 @@
         @Retention(RetentionPolicy.SOURCE)
         @interface Flag {}
 
-        // Usually "post" change state.
+        /**
+         * "Parent" that is also included in the transition. When populating the parent changes, we
+         * may skip the intermediate parents, so this may not be the actual parent in the hierarchy.
+         */
         WindowContainer mEndParent;
-        // Parent before change state.
+        /** Actual parent window before change state. */
         WindowContainer mStartParent;
+        /**
+         * When the window is reparented during the transition, this is the common ancestor window
+         * of the {@link #mStartParent} and the current parent. This is needed because the
+         * {@link #mStartParent} may have been detached when the transition starts.
+         */
+        WindowContainer mCommonAncestor;
 
         // State tracking
         boolean mExistenceChanged = false;
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index ac85c9a..37bef3a 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -533,6 +533,17 @@
         mCollectingTransition.collectVisibleChange(wc);
     }
 
+    /**
+     * Records that a particular container has been reparented. This only effects windows that have
+     * already been collected in the transition. This should be called before reparenting because
+     * the old parent may be removed during reparenting, for example:
+     * {@link Task#shouldRemoveSelfOnLastChildRemoval}
+     */
+    void collectReparentChange(@NonNull WindowContainer wc, @NonNull WindowContainer newParent) {
+        if (!isCollecting()) return;
+        mCollectingTransition.collectReparentChange(wc, newParent);
+    }
+
     /** @see Transition#mStatusBarTransitionDelay */
     void setStatusBarTransitionDelay(long delay) {
         if (mCollectingTransition == null) return;
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 0b5de85..73d4496 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -542,6 +542,10 @@
             throw new IllegalArgumentException("WC=" + this + " already child of " + mParent);
         }
 
+        // Collect before removing child from old parent, because the old parent may be removed if
+        // this is the last child in it.
+        mTransitionController.collectReparentChange(this, newParent);
+
         // The display object before reparenting as that might lead to old parent getting removed
         // from the display if it no longer has any child.
         final DisplayContent prevDc = oldParent.getDisplayContent();
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 3590e9c2..7415376 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -1547,6 +1547,12 @@
         return mTransitionController.mTransitionMetricsReporter;
     }
 
+    @Override
+    public IBinder getApplyToken() {
+        enforceTaskPermission("getApplyToken()");
+        return SurfaceControl.Transaction.getDefaultApplyToken();
+    }
+
     /** Whether the configuration changes are important to report back to an organizer. */
     static boolean configurationsAreEqualForOrganizer(
             Configuration newConfig, @Nullable Configuration oldConfig) {
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 6d750a4..0168d50 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -29,7 +29,6 @@
 import static android.os.PowerManager.DRAW_WAKE_LOCK;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
 import static android.view.InsetsState.ITYPE_IME;
-import static android.view.InsetsState.ITYPE_INVALID;
 import static android.view.SurfaceControl.Transaction;
 import static android.view.SurfaceControl.getGlobalTransaction;
 import static android.view.ViewRootImpl.LOCAL_LAYOUT;
@@ -1673,14 +1672,11 @@
         if (rotatedState != null) {
             return insetsPolicy.adjustInsetsForWindow(this, rotatedState);
         }
-        final InsetsSourceProvider provider = getControllableInsetProvider();
-        final @InternalInsetsType int insetTypeProvidedByWindow = provider != null
-                ? provider.getSource().getType() : ITYPE_INVALID;
         final InsetsState rawInsetsState =
                 mFrozenInsetsState != null ? mFrozenInsetsState : getMergedInsetsState();
         final InsetsState insetsStateForWindow = insetsPolicy
-                .enforceInsetsPolicyForTarget(insetTypeProvidedByWindow,
-                        getWindowingMode(), isAlwaysOnTop(), mAttrs.type, rawInsetsState);
+                .enforceInsetsPolicyForTarget(
+                        getWindowingMode(), isAlwaysOnTop(), mAttrs, rawInsetsState);
         return insetsPolicy.adjustInsetsForWindow(this, insetsStateForWindow,
                 includeTransient);
     }
@@ -1842,8 +1838,8 @@
      * @return {@code true} if one or more windows have been displayed, else false.
      */
     boolean hasAppShownWindows() {
-        return mActivityRecord != null
-                && (mActivityRecord.firstWindowDrawn || mActivityRecord.startingDisplayed);
+        return mActivityRecord != null && (mActivityRecord.firstWindowDrawn
+                || mActivityRecord.isStartingWindowDisplayed());
     }
 
     @Override
@@ -5713,6 +5709,15 @@
         return super.getAnimationLeashParent();
     }
 
+    @Override
+    public void onAnimationLeashCreated(Transaction t, SurfaceControl leash) {
+        super.onAnimationLeashCreated(t, leash);
+        if (isStartingWindowAssociatedToTask()) {
+            // Make sure the animation leash is still on top of the task.
+            t.setLayer(leash, Integer.MAX_VALUE);
+        }
+    }
+
     // TODO(b/70040778): We should aim to eliminate the last user of TYPE_APPLICATION_MEDIA
     // then we can drop all negative layering on the windowing side and simply inherit
     // the default implementation here.
diff --git a/services/core/java/com/android/server/wm/WindowStateAnimator.java b/services/core/java/com/android/server/wm/WindowStateAnimator.java
index 5c0557f..a0ba8fd 100644
--- a/services/core/java/com/android/server/wm/WindowStateAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowStateAnimator.java
@@ -374,13 +374,6 @@
     }
 
     void destroySurfaceLocked(SurfaceControl.Transaction t) {
-        final ActivityRecord activity = mWin.mActivityRecord;
-        if (activity != null) {
-            if (mWin == activity.mStartingWindow) {
-                activity.startingDisplayed = false;
-            }
-        }
-
         if (mSurfaceController == null) {
             return;
         }
@@ -602,11 +595,17 @@
             return true;
         }
 
-        final boolean isImeWindow = mWin.mAttrs.type == TYPE_INPUT_METHOD;
-        if (isEntrance && isImeWindow) {
+        if (mWin.mAttrs.type == TYPE_INPUT_METHOD) {
             mWin.getDisplayContent().adjustForImeIfNeeded();
-            mWin.setDisplayLayoutNeeded();
-            mService.mWindowPlacerLocked.requestTraversal();
+            if (isEntrance) {
+                mWin.setDisplayLayoutNeeded();
+                mService.mWindowPlacerLocked.requestTraversal();
+            }
+        }
+
+        if (mWin.mControllableInsetProvider != null) {
+            // All our animations should be driven by the insets control target.
+            return false;
         }
 
         // Only apply an animation if the display isn't frozen.  If it is
@@ -654,14 +653,10 @@
                 Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
                 mAnimationIsEntrance = isEntrance;
             }
-        } else if (!isImeWindow) {
+        } else {
             mWin.cancelAnimation();
         }
 
-        if (!isEntrance && isImeWindow) {
-            mWin.getDisplayContent().adjustForImeIfNeeded();
-        }
-
         return mWin.isAnimating(0 /* flags */, ANIMATION_TYPE_WINDOW_ANIMATION);
     }
 
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 3f380e7..0d87237 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -107,6 +107,7 @@
     jmethodID notifyFocusChanged;
     jmethodID notifySensorEvent;
     jmethodID notifySensorAccuracy;
+    jmethodID notifyStylusGestureStarted;
     jmethodID notifyVibratorState;
     jmethodID filterInputEvent;
     jmethodID interceptKeyBeforeQueueing;
@@ -299,6 +300,7 @@
     void requestPointerCapture(const sp<IBinder>& windowToken, bool enabled);
     void setCustomPointerIcon(const SpriteIcon& icon);
     void setMotionClassifierEnabled(bool enabled);
+    std::optional<std::string> getBluetoothAddress(int32_t deviceId);
 
     /* --- InputReaderPolicyInterface implementation --- */
 
@@ -312,6 +314,7 @@
                                                            int32_t surfaceRotation) override;
 
     TouchAffineTransformation getTouchAffineTransformation(JNIEnv* env, jfloatArray matrixArr);
+    void notifyStylusGestureStarted(int32_t deviceId, nsecs_t eventTime) override;
 
     /* --- InputDispatcherPolicyInterface implementation --- */
 
@@ -370,37 +373,37 @@
     Mutex mLock;
     struct Locked {
         // Display size information.
-        std::vector<DisplayViewport> viewports;
+        std::vector<DisplayViewport> viewports{};
 
         // True if System UI is less noticeable.
-        bool systemUiLightsOut;
+        bool systemUiLightsOut{false};
 
         // Pointer speed.
-        int32_t pointerSpeed;
+        int32_t pointerSpeed{0};
 
         // Pointer acceleration.
-        float pointerAcceleration;
+        float pointerAcceleration{android::os::IInputConstants::DEFAULT_POINTER_ACCELERATION};
 
         // True if pointer gestures are enabled.
-        bool pointerGesturesEnabled;
+        bool pointerGesturesEnabled{true};
 
         // Show touches feature enable/disable.
-        bool showTouches;
+        bool showTouches{false};
 
         // The latest request to enable or disable Pointer Capture.
-        PointerCaptureRequest pointerCaptureRequest;
+        PointerCaptureRequest pointerCaptureRequest{};
 
         // Sprite controller singleton, created on first use.
-        sp<SpriteController> spriteController;
+        sp<SpriteController> spriteController{};
 
         // Pointer controller singleton, created and destroyed as needed.
-        std::weak_ptr<PointerController> pointerController;
+        std::weak_ptr<PointerController> pointerController{};
 
         // Input devices to be disabled
-        std::set<int32_t> disabledInputDevices;
+        std::set<int32_t> disabledInputDevices{};
 
         // Associated Pointer controller display.
-        int32_t pointerDisplayId;
+        int32_t pointerDisplayId{ADISPLAY_ID_DEFAULT};
     } mLocked GUARDED_BY(mLock);
 
     std::atomic<bool> mInteractive;
@@ -419,16 +422,6 @@
 
     mServiceObj = env->NewGlobalRef(serviceObj);
 
-    {
-        AutoMutex _l(mLock);
-        mLocked.systemUiLightsOut = false;
-        mLocked.pointerSpeed = 0;
-        mLocked.pointerAcceleration = android::os::IInputConstants::DEFAULT_POINTER_ACCELERATION;
-        mLocked.pointerGesturesEnabled = true;
-        mLocked.showTouches = false;
-        mLocked.pointerDisplayId = ADISPLAY_ID_DEFAULT;
-    }
-    mInteractive = true;
     InputManager* im = new InputManager(this, this);
     mInputManager = im;
     defaultServiceManager()->addService(String16("inputflinger"), im);
@@ -1177,6 +1170,13 @@
     return transform;
 }
 
+void NativeInputManager::notifyStylusGestureStarted(int32_t deviceId, nsecs_t eventTime) {
+    JNIEnv* env = jniEnv();
+    env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyStylusGestureStarted, deviceId,
+                        eventTime);
+    checkAndClearExceptionFromCallback(env, "notifyStylusGestureStarted");
+}
+
 bool NativeInputManager::filterInputEvent(const InputEvent* inputEvent, uint32_t policyFlags) {
     ATRACE_CALL();
     jobject inputEventObj;
@@ -1487,6 +1487,10 @@
     mInputManager->getProcessor().setMotionClassifierEnabled(enabled);
 }
 
+std::optional<std::string> NativeInputManager::getBluetoothAddress(int32_t deviceId) {
+    return mInputManager->getReader().getBluetoothAddress(deviceId);
+}
+
 bool NativeInputManager::isPerDisplayTouchModeEnabled() {
     JNIEnv* env = jniEnv();
     jboolean enabled =
@@ -2326,6 +2330,12 @@
     im->setPointerDisplayId(displayId);
 }
 
+static jstring nativeGetBluetoothAddress(JNIEnv* env, jobject nativeImplObj, jint deviceId) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+    const auto address = im->getBluetoothAddress(deviceId);
+    return address ? env->NewStringUTF(address->c_str()) : nullptr;
+}
+
 // ----------------------------------------------------------------------------
 
 static const JNINativeMethod gInputManagerMethods[] = {
@@ -2408,6 +2418,7 @@
         {"flushSensor", "(II)Z", (void*)nativeFlushSensor},
         {"cancelCurrentTouch", "()V", (void*)nativeCancelCurrentTouch},
         {"setPointerDisplayId", "(I)V", (void*)nativeSetPointerDisplayId},
+        {"getBluetoothAddress", "(I)Ljava/lang/String;", (void*)nativeGetBluetoothAddress},
 };
 
 #define FIND_CLASS(var, className) \
@@ -2469,6 +2480,9 @@
 
     GET_METHOD_ID(gServiceClassInfo.notifySensorAccuracy, clazz, "notifySensorAccuracy", "(III)V");
 
+    GET_METHOD_ID(gServiceClassInfo.notifyStylusGestureStarted, clazz, "notifyStylusGestureStarted",
+                  "(IJ)V");
+
     GET_METHOD_ID(gServiceClassInfo.notifyVibratorState, clazz, "notifyVibratorState", "(IZ)V");
 
     GET_METHOD_ID(gServiceClassInfo.notifyNoFocusedWindowAnr, clazz, "notifyNoFocusedWindowAnr",
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 91f5c69..321f022 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -21,20 +21,29 @@
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.credentials.CreateCredentialRequest;
 import android.credentials.GetCredentialRequest;
+import android.credentials.IClearCredentialSessionCallback;
 import android.credentials.ICreateCredentialCallback;
 import android.credentials.ICredentialManager;
 import android.credentials.IGetCredentialCallback;
+import android.os.Binder;
 import android.os.CancellationSignal;
 import android.os.ICancellationSignal;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.text.TextUtils;
 import android.util.Log;
+import android.util.Slog;
 
 import com.android.server.infra.AbstractMasterSystemService;
 import com.android.server.infra.SecureSettingsServiceNameResolver;
 
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
 /**
  * Entry point service for credential management.
  *
@@ -49,56 +58,112 @@
 
     public CredentialManagerService(@NonNull Context context) {
         super(context,
-                new SecureSettingsServiceNameResolver(context, Settings.Secure.AUTOFILL_SERVICE),
+                new SecureSettingsServiceNameResolver(context, Settings.Secure.CREDENTIAL_SERVICE,
+                        /*isMultipleMode=*/true),
                 null, PACKAGE_UPDATE_POLICY_REFRESH_EAGER);
     }
 
     @Override
     protected String getServiceSettingsProperty() {
-        return Settings.Secure.AUTOFILL_SERVICE;
+        return Settings.Secure.CREDENTIAL_SERVICE;
     }
 
     @Override // from AbstractMasterSystemService
     protected CredentialManagerServiceImpl newServiceLocked(@UserIdInt int resolvedUserId,
             boolean disabled) {
-        return new CredentialManagerServiceImpl(this, mLock, resolvedUserId);
+        // This method should not be called for CredentialManagerService as it is configured to use
+        // multiple services.
+        Slog.w(TAG, "Should not be here - CredentialManagerService is configured to use "
+                + "multiple services");
+        return null;
     }
 
-    @Override
+    @Override // from SystemService
     public void onStart() {
-        Log.i(TAG, "onStart");
         publishBinderService(CREDENTIAL_SERVICE, new CredentialManagerServiceStub());
     }
 
+    @Override // from AbstractMasterSystemService
+    protected List<CredentialManagerServiceImpl> newServiceListLocked(int resolvedUserId,
+            boolean disabled, String[] serviceNames) {
+        if (serviceNames == null || serviceNames.length == 0) {
+            Slog.i(TAG, "serviceNames sent in newServiceListLocked is null, or empty");
+            return new ArrayList<>();
+        }
+        List<CredentialManagerServiceImpl> serviceList = new ArrayList<>(serviceNames.length);
+        for (int i = 0; i < serviceNames.length; i++) {
+            Log.i(TAG, "in newServiceListLocked, service: " + serviceNames[i]);
+            if (TextUtils.isEmpty(serviceNames[i])) {
+                continue;
+            }
+            try {
+                serviceList.add(new CredentialManagerServiceImpl(this, mLock, resolvedUserId,
+                        serviceNames[i]));
+            } catch (PackageManager.NameNotFoundException e) {
+                Log.i(TAG, "Unable to add serviceInfo : " + e.getMessage());
+            } catch (SecurityException e) {
+                Log.i(TAG, "Unable to add serviceInfo : " + e.getMessage());
+            }
+        }
+        return serviceList;
+    }
+
+    private void runForUser(@NonNull final Consumer<CredentialManagerServiceImpl> c) {
+        final int userId = UserHandle.getCallingUserId();
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            synchronized (mLock) {
+                final List<CredentialManagerServiceImpl> services =
+                        getServiceListForUserLocked(userId);
+                services.forEach(s -> {
+                    c.accept(s);
+                });
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
     final class CredentialManagerServiceStub extends ICredentialManager.Stub {
         @Override
         public ICancellationSignal executeGetCredential(
                 GetCredentialRequest request,
-                IGetCredentialCallback callback) {
-            // TODO: implement.
-            Log.i(TAG, "executeGetCredential");
-
-            final int userId = UserHandle.getCallingUserId();
-            synchronized (mLock) {
-                final CredentialManagerServiceImpl service = peekServiceForUserLocked(userId);
-                if (service != null) {
-                    Log.i(TAG, "Got service for : " + userId);
-                    service.getCredential();
-                }
-            }
-
+                IGetCredentialCallback callback,
+                final String callingPackage) {
+            Log.i(TAG, "starting executeGetCredential with callingPackage: " + callingPackage);
+            // TODO : Implement cancellation
             ICancellationSignal cancelTransport = CancellationSignal.createTransport();
+
+            // New request session, scoped for this request only.
+            final GetRequestSession session = new GetRequestSession(getContext(),
+                    UserHandle.getCallingUserId(),
+                    callback);
+
+            // Invoke all services of a user
+            runForUser((service) -> {
+                service.getCredential(request, session, callingPackage);
+            });
             return cancelTransport;
         }
 
         @Override
         public ICancellationSignal executeCreateCredential(
                 CreateCredentialRequest request,
-                ICreateCredentialCallback callback) {
+                ICreateCredentialCallback callback,
+                String callingPackage) {
             // TODO: implement.
             Log.i(TAG, "executeCreateCredential");
             ICancellationSignal cancelTransport = CancellationSignal.createTransport();
             return cancelTransport;
         }
+
+        @Override
+        public ICancellationSignal clearCredentialSession(
+                IClearCredentialSessionCallback callback, String callingPackage) {
+            // TODO: implement.
+            Log.i(TAG, "clearCredentialSession");
+            ICancellationSignal cancelTransport = CancellationSignal.createTransport();
+            return cancelTransport;
+        }
     }
 }
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java b/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
index aa19241..cc03f9b 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerServiceImpl.java
@@ -17,47 +17,94 @@
 package com.android.server.credentials;
 
 import android.annotation.NonNull;
-import android.app.AppGlobals;
+import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.pm.PackageManager;
 import android.content.pm.ServiceInfo;
-import android.os.RemoteException;
-import android.util.Log;
+import android.credentials.GetCredentialRequest;
+import android.service.credentials.CredentialProviderInfo;
+import android.service.credentials.GetCredentialsRequest;
+import android.util.Slog;
 
 import com.android.server.infra.AbstractPerUserSystemService;
 
+
 /**
- * Per-user implementation of {@link CredentialManagerService}
+ * Per-user, per remote service implementation of {@link CredentialManagerService}
  */
 public final class CredentialManagerServiceImpl extends
         AbstractPerUserSystemService<CredentialManagerServiceImpl, CredentialManagerService> {
     private static final String TAG = "CredManSysServiceImpl";
 
-    protected CredentialManagerServiceImpl(
+    // TODO(b/210531) : Make final when update flow is fixed
+    private ComponentName mRemoteServiceComponentName;
+    private CredentialProviderInfo mInfo;
+
+    public CredentialManagerServiceImpl(
             @NonNull CredentialManagerService master,
-            @NonNull Object lock, int userId) {
+            @NonNull Object lock, int userId, String serviceName)
+            throws PackageManager.NameNotFoundException {
         super(master, lock, userId);
+        Slog.i(TAG, "in CredentialManagerServiceImpl cons");
+        // TODO : Replace with newServiceInfoLocked after confirming behavior
+        mRemoteServiceComponentName = ComponentName.unflattenFromString(serviceName);
+        mInfo = new CredentialProviderInfo(getContext(), mRemoteServiceComponentName, mUserId);
     }
 
     @Override // from PerUserSystemService
     protected ServiceInfo newServiceInfoLocked(@NonNull ComponentName serviceComponent)
             throws PackageManager.NameNotFoundException {
-        ServiceInfo si;
-        try {
-            si = AppGlobals.getPackageManager().getServiceInfo(serviceComponent,
-                    PackageManager.GET_META_DATA, mUserId);
-        } catch (RemoteException e) {
-            throw new PackageManager.NameNotFoundException(
-                    "Could not get service for " + serviceComponent);
-        }
-        return si;
+        // TODO : Test update flows with multiple providers
+        Slog.i(TAG , "newServiceInfoLocked with : " + serviceComponent.getPackageName());
+        mRemoteServiceComponentName = serviceComponent;
+        mInfo = new CredentialProviderInfo(getContext(), serviceComponent, mUserId);
+        return mInfo.getServiceInfo();
     }
 
-    /**
-     * Unimplemented getCredentials
-     */
-    public void getCredential() {
-        Log.i(TAG, "getCredential not implemented");
-        // TODO : Implement logic
+    public void getCredential(GetCredentialRequest request, GetRequestSession requestSession,
+            String callingPackage) {
+        Slog.i(TAG, "in getCredential in CredManServiceImpl");
+        if (mInfo == null) {
+            Slog.i(TAG, "in getCredential in CredManServiceImpl, but mInfo is null");
+            return;
+        }
+
+        // TODO : Determine if remoteService instance can be reused across requests
+        final RemoteCredentialService remoteService = new RemoteCredentialService(
+                getContext(), mInfo.getServiceInfo().getComponentName(), mUserId);
+        ProviderGetSession providerSession = new ProviderGetSession(mInfo,
+                requestSession, mUserId, remoteService);
+        // Set the provider info to the session when the request is initiated. This happens here
+        // because there is one serviceImpl per remote provider, and so we can only retrieve
+        // the provider information in the scope of this instance, whereas the session is for the
+        // entire request.
+        requestSession.addProviderSession(providerSession);
+        GetCredentialsRequest filteredRequest = getRequestWithValidType(request, callingPackage);
+        if (filteredRequest != null) {
+            remoteService.onGetCredentials(getRequestWithValidType(request, callingPackage),
+                    providerSession);
+        }
+    }
+
+    @Nullable
+    private GetCredentialsRequest getRequestWithValidType(GetCredentialRequest request,
+            String callingPackage) {
+        GetCredentialsRequest.Builder builder =
+                new GetCredentialsRequest.Builder(callingPackage);
+        request.getGetCredentialOptions().forEach( option -> {
+            if (mInfo.hasCapability(option.getType())) {
+                Slog.i(TAG, "Provider can handle: " + option.getType());
+                builder.addGetCredentialOption(option);
+            } else {
+                Slog.i(TAG, "Skipping request as provider cannot handle it");
+            }
+        });
+
+        try {
+            return builder.build();
+        } catch (IllegalArgumentException | NullPointerException e) {
+            Slog.i(TAG, "issue with request build: " + e.getMessage());
+        }
+        return null;
     }
 }
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
new file mode 100644
index 0000000..dcf094f
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.credentials;
+
+import android.annotation.NonNull;
+import android.content.Context;
+import android.content.Intent;
+import android.credentials.ui.IntentFactory;
+import android.credentials.ui.ProviderData;
+import android.credentials.ui.RequestInfo;
+import android.credentials.ui.UserSelectionDialogResult;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.ResultReceiver;
+import android.util.Log;
+import android.util.Slog;
+
+import java.util.ArrayList;
+
+/** Initiates the Credential Manager UI and receives results. */
+public class CredentialManagerUi {
+    private static final String TAG = "CredentialManagerUi";
+    @NonNull
+    private final CredentialManagerUiCallback mCallbacks;
+    @NonNull private final Context mContext;
+    private final int mUserId;
+    @NonNull private final ResultReceiver mResultReceiver = new ResultReceiver(
+            new Handler(Looper.getMainLooper())) {
+        @Override
+        protected void onReceiveResult(int resultCode, Bundle resultData) {
+            handleUiResult(resultCode, resultData);
+        }
+    };
+
+    private void handleUiResult(int resultCode, Bundle resultData) {
+        if (resultCode == UserSelectionDialogResult.RESULT_CODE_DIALOG_COMPLETE_WITH_SELECTION) {
+            UserSelectionDialogResult selection = UserSelectionDialogResult
+                    .fromResultData(resultData);
+            if (selection != null) {
+                mCallbacks.onUiSelection(selection);
+            } else {
+                Slog.i(TAG, "No selection found in UI result");
+            }
+        } else if (resultCode == UserSelectionDialogResult.RESULT_CODE_DIALOG_CANCELED) {
+            mCallbacks.onUiCancelation();
+        }
+    }
+
+    /**
+     * Interface to be implemented by any class that wishes to get callbacks from the UI.
+     */
+    public interface CredentialManagerUiCallback {
+        /** Called when the user makes a selection. */
+        void onUiSelection(UserSelectionDialogResult selection);
+        /** Called when the user cancels the UI. */
+        void onUiCancelation();
+    }
+    public CredentialManagerUi(Context context, int userId,
+            CredentialManagerUiCallback callbacks) {
+        Log.i(TAG, "In CredentialManagerUi constructor");
+        mContext = context;
+        mUserId = userId;
+        mCallbacks = callbacks;
+    }
+
+    /**
+     * Surfaces the Credential Manager bottom sheet UI.
+     * @param providerDataList the list of provider data from remote providers
+     */
+    public void show(RequestInfo requestInfo, ArrayList<ProviderData> providerDataList) {
+        Log.i(TAG, "In show");
+        Intent intent = IntentFactory.newIntent(
+                requestInfo, providerDataList,
+                new ArrayList<>(), mResultReceiver);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        mContext.startActivity(intent);
+    }
+}
diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
new file mode 100644
index 0000000..80f0fec
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
@@ -0,0 +1,164 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.credentials;
+
+import android.content.ComponentName;
+import android.content.Context;
+import android.credentials.Credential;
+import android.credentials.GetCredentialResponse;
+import android.credentials.IGetCredentialCallback;
+import android.credentials.ui.ProviderData;
+import android.credentials.ui.RequestInfo;
+import android.credentials.ui.UserSelectionDialogResult;
+import android.os.RemoteException;
+import android.service.credentials.CredentialEntry;
+import android.util.Log;
+import android.util.Slog;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicBoolean;
+
+/**
+ * Central session for a single getCredentials request. This class listens to the
+ * responses from providers, and the UX app, and updates the provider(S) state.
+ */
+public final class GetRequestSession extends RequestSession {
+    private static final String TAG = "GetRequestSession";
+
+    private final IGetCredentialCallback mClientCallback;
+    private final Map<String, ProviderGetSession> mProviders;
+
+    public GetRequestSession(Context context, int userId,
+            IGetCredentialCallback callback) {
+        super(context, userId, RequestInfo.TYPE_GET);
+        mClientCallback = callback;
+        mProviders = new HashMap<>();
+    }
+
+    /**
+     * Adds a new provider to the list of providers that are contributing to this session.
+     */
+    public void addProviderSession(ProviderGetSession providerSession) {
+        mProviders.put(providerSession.getComponentName().flattenToString(),
+                providerSession);
+    }
+
+    @Override
+    public void onProviderStatusChanged(ProviderSession.Status status,
+            ComponentName componentName) {
+        Log.i(TAG, "in onStatusChanged");
+        if (ProviderSession.isTerminatingStatus(status)) {
+            Log.i(TAG, "in onStatusChanged terminating status");
+
+            ProviderGetSession session = mProviders.remove(componentName.flattenToString());
+            if (session != null) {
+                Slog.i(TAG, "Provider session removed.");
+            } else {
+                Slog.i(TAG, "Provider session null, did not exist.");
+            }
+        } else if (ProviderSession.isCompletionStatus(status)) {
+            Log.i(TAG, "in onStatusChanged isCompletionStatus status");
+            onProviderResponseComplete();
+        }
+    }
+
+    @Override
+    public void onUiSelection(UserSelectionDialogResult selection) {
+        String providerId = selection.getProviderId();
+        ProviderGetSession providerSession = mProviders.get(providerId);
+        if (providerSession != null) {
+            CredentialEntry credentialEntry = providerSession.getCredentialEntry(
+                    selection.getEntrySubkey());
+            if (credentialEntry != null && credentialEntry.getCredential() != null) {
+                respondToClientAndFinish(credentialEntry.getCredential());
+            }
+            // TODO : Handle action chips and authentication selection
+            return;
+        }
+        // TODO : finish session and respond to client if provider not found
+    }
+
+    @Override
+    public void onUiCancelation() {
+        // User canceled the activity
+        // TODO : Send error code to client
+        finishSession();
+    }
+
+    private void onProviderResponseComplete() {
+        Log.i(TAG, "in onProviderResponseComplete");
+        if (isResponseCompleteAcrossProviders()) {
+            Log.i(TAG, "in onProviderResponseComplete - isResponseCompleteAcrossProviders");
+            getProviderDataAndInitiateUi();
+        }
+    }
+
+    private void getProviderDataAndInitiateUi() {
+        ArrayList<ProviderData> providerDataList = new ArrayList<>();
+        for (ProviderGetSession session : mProviders.values()) {
+            Log.i(TAG, "preparing data for : " + session.getComponentName());
+            providerDataList.add(session.prepareUiData());
+        }
+        if (!providerDataList.isEmpty()) {
+            Log.i(TAG, "provider list not empty about to initiate ui");
+            initiateUi(providerDataList);
+        }
+    }
+
+    private void initiateUi(ArrayList<ProviderData> providerDataList) {
+        mHandler.post(() -> mCredentialManagerUi.show(RequestInfo.newGetRequestInfo(
+                mRequestId, null, mIsFirstUiTurn, ""),
+                providerDataList));
+    }
+
+    /**
+     * Iterates over all provider sessions and returns true if all have responded.
+     */
+    private boolean isResponseCompleteAcrossProviders() {
+        AtomicBoolean isRequestComplete = new AtomicBoolean(true);
+        mProviders.forEach( (packageName, session) -> {
+            if (session.getStatus() != ProviderSession.Status.COMPLETE) {
+                isRequestComplete.set(false);
+            }
+        });
+        return isRequestComplete.get();
+    }
+
+    private void respondToClientAndFinish(Credential credential) {
+        try {
+            mClientCallback.onResponse(new GetCredentialResponse(credential));
+        } catch (RemoteException e) {
+            e.printStackTrace();
+        }
+        finishSession();
+    }
+
+    private void finishSession() {
+        clearProviderSessions();
+    }
+
+    private void clearProviderSessions() {
+        for (ProviderGetSession session : mProviders.values()) {
+            // TODO : Evaluate if we should unbind remote services here or wait for them
+            // to automatically unbind when idle. Re-binding frequently also has a cost.
+            //session.destroy();
+        }
+        mProviders.clear();
+    }
+}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderGetSession.java b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
new file mode 100644
index 0000000..ff2107a
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/ProviderGetSession.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.credentials;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.slice.Slice;
+import android.credentials.ui.Entry;
+import android.credentials.ui.GetCredentialProviderData;
+import android.service.credentials.Action;
+import android.service.credentials.CredentialEntry;
+import android.service.credentials.CredentialProviderInfo;
+import android.service.credentials.CredentialsDisplayContent;
+import android.service.credentials.GetCredentialsResponse;
+import android.util.Log;
+import android.util.Slog;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+/**
+ * Central provider session that listens for provider callbacks, and maintains provider state.
+ * Will likely split this into remote response state and UI state.
+ */
+public final class ProviderGetSession extends ProviderSession<GetCredentialsResponse>
+        implements RemoteCredentialService.ProviderCallbacks<GetCredentialsResponse> {
+    private static final String TAG = "ProviderGetSession";
+
+    // Key to be used as an entry key for a credential entry
+    private static final String CREDENTIAL_ENTRY_KEY = "credential_key";
+
+    private GetCredentialsResponse mResponse;
+
+    @NonNull
+    private final Map<String, CredentialEntry> mUiCredentials = new HashMap<>();
+
+    @NonNull
+    private final Map<String, Action> mUiActions = new HashMap<>();
+
+    public ProviderGetSession(CredentialProviderInfo info,
+            ProviderInternalCallback callbacks,
+            int userId, RemoteCredentialService remoteCredentialService) {
+        super(info, callbacks, userId, remoteCredentialService);
+        setStatus(Status.PENDING);
+    }
+
+    /** Updates the response being maintained in state by this provider session. */
+    @Override
+    public void updateResponse(GetCredentialsResponse response) {
+        if (response.getAuthenticationAction() != null) {
+            // TODO : Implement authentication logic
+        } else if (response.getCredentialsDisplayContent() != null) {
+            Log.i(TAG , "updateResponse with credentialEntries");
+            mResponse = response;
+            updateStatusAndInvokeCallback(Status.COMPLETE);
+        }
+    }
+
+    /** Returns the response being maintained in this provider session. */
+    @Override
+    @Nullable
+    public GetCredentialsResponse getResponse() {
+        return  mResponse;
+    }
+
+    /** Returns the credential entry maintained in state by this provider session. */
+    @Nullable
+    public CredentialEntry getCredentialEntry(@NonNull String entryId) {
+        return mUiCredentials.get(entryId);
+    }
+
+    /** Returns the action entry maintained in state by this provider session. */
+    @Nullable
+    public Action getAction(@NonNull String entryId) {
+        return mUiActions.get(entryId);
+    }
+
+    /** Called when the provider response has been updated by an external source. */
+    @Override
+    public void onProviderResponseSuccess(@Nullable GetCredentialsResponse response) {
+        Log.i(TAG, "in onProviderResponseSuccess");
+        updateResponse(response);
+    }
+
+    /** Called when the provider response resulted in a failure. */
+    @Override
+    public void onProviderResponseFailure(int errorCode, @Nullable CharSequence message) {
+        updateStatusAndInvokeCallback(toStatus(errorCode));
+    }
+
+    /** Called when provider service dies. */
+    @Override
+    public void onProviderServiceDied(RemoteCredentialService service) {
+        if (service.getComponentName().equals(mProviderInfo.getServiceInfo().getComponentName())) {
+            updateStatusAndInvokeCallback(Status.SERVICE_DEAD);
+        } else {
+            Slog.i(TAG, "Component names different in onProviderServiceDied - "
+                    + "this should not happen");
+        }
+    }
+
+    @Override
+    protected GetCredentialProviderData prepareUiData() throws IllegalArgumentException {
+        Log.i(TAG, "In prepareUiData");
+        if (!ProviderSession.isCompletionStatus(getStatus())) {
+            Log.i(TAG, "In prepareUiData not complete");
+
+            throw new IllegalStateException("Status must be in completion mode");
+        }
+        GetCredentialsResponse response = getResponse();
+        if (response == null) {
+            Log.i(TAG, "In prepareUiData response null");
+
+            throw new IllegalStateException("Response must be in completion mode");
+        }
+        if (response.getAuthenticationAction() != null) {
+            Log.i(TAG, "In prepareUiData auth not null");
+
+            return prepareUiProviderDataWithAuthentication(response.getAuthenticationAction());
+        }
+        if (response.getCredentialsDisplayContent() != null){
+            Log.i(TAG, "In prepareUiData credentials not null");
+
+            return prepareUiProviderDataWithCredentials(response.getCredentialsDisplayContent());
+        }
+        return null;
+    }
+
+    /**
+     * To be called by {@link ProviderGetSession} when the UI is to be invoked.
+     */
+    @Nullable
+    private GetCredentialProviderData prepareUiProviderDataWithCredentials(@NonNull
+            CredentialsDisplayContent content) {
+        Log.i(TAG, "in prepareUiProviderData");
+        List<Entry> credentialEntries = new ArrayList<>();
+        List<Entry> actionChips = new ArrayList<>();
+        Entry authenticationEntry = null;
+
+        // Populate the credential entries
+        for (CredentialEntry credentialEntry : content.getCredentialEntries()) {
+            String entryId = UUID.randomUUID().toString();
+            mUiCredentials.put(entryId, credentialEntry);
+            Log.i(TAG, "in prepareUiProviderData creating ui entry with id " + entryId);
+            Slice slice = credentialEntry.getSlice();
+            // TODO : Remove conversion of string to int after change in Entry class
+            credentialEntries.add(new Entry(CREDENTIAL_ENTRY_KEY, entryId,
+                    credentialEntry.getSlice()));
+        }
+        // populate the action chip
+        for (Action action : content.getActions()) {
+            String entryId = UUID.randomUUID().toString();
+            mUiActions.put(entryId, action);
+            // TODO : Remove conversion of string to int after change in Entry class
+            actionChips.add(new Entry(ACTION_ENTRY_KEY, entryId,
+                    action.getSlice()));
+        }
+
+        return new GetCredentialProviderData.Builder(mComponentName.flattenToString())
+                .setCredentialEntries(credentialEntries)
+                .setActionChips(actionChips)
+                .setAuthenticationEntry(authenticationEntry)
+                .build();
+    }
+
+    /**
+     * To be called by {@link ProviderGetSession} when the UI is to be invoked.
+     */
+    @Nullable
+    private GetCredentialProviderData prepareUiProviderDataWithAuthentication(@NonNull
+            Action authenticationEntry) {
+        // TODO : Implement authentication flow
+        return null;
+    }
+}
diff --git a/services/credentials/java/com/android/server/credentials/ProviderSession.java b/services/credentials/java/com/android/server/credentials/ProviderSession.java
new file mode 100644
index 0000000..3a9f964
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/ProviderSession.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.credentials;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.credentials.ui.ProviderData;
+import android.service.credentials.CredentialProviderException;
+import android.service.credentials.CredentialProviderInfo;
+
+/**
+ * Provider session storing the state of provider response and ui entries.
+ * @param <T> The request type expected from the remote provider, for a given request session.
+ */
+public abstract class ProviderSession<T> implements RemoteCredentialService.ProviderCallbacks<T> {
+    // Key to be used as the entry key for an action entry
+    protected static final String ACTION_ENTRY_KEY = "action_key";
+
+    @NonNull protected final ComponentName mComponentName;
+    @NonNull protected final CredentialProviderInfo mProviderInfo;
+    @NonNull protected final RemoteCredentialService mRemoteCredentialService;
+    @NonNull protected final int mUserId;
+    @NonNull protected Status mStatus = Status.NOT_STARTED;
+    @NonNull protected final ProviderInternalCallback mCallbacks;
+
+    /**
+     * Interface to be implemented by any class that wishes to get a callback when a particular
+     * provider session's status changes. Typically, implemented by the {@link RequestSession}
+     * class.
+     */
+    public interface ProviderInternalCallback {
+        /**
+         * Called when status changes.
+         */
+        void onProviderStatusChanged(Status status, ComponentName componentName);
+    }
+
+    protected ProviderSession(@NonNull CredentialProviderInfo info,
+            @NonNull ProviderInternalCallback callbacks,
+            @NonNull int userId,
+            @NonNull RemoteCredentialService remoteCredentialService) {
+        mProviderInfo = info;
+        mCallbacks = callbacks;
+        mUserId = userId;
+        mComponentName = info.getServiceInfo().getComponentName();
+        mRemoteCredentialService = remoteCredentialService;
+    }
+
+    /** Update the response state stored with the provider session. */
+    protected abstract void updateResponse (T response);
+
+    /** Update the response state stored with the provider session. */
+    protected abstract T getResponse ();
+
+    /** Should be overridden to prepare, and stores state for {@link ProviderData} to be
+     * shown on the UI. */
+    protected abstract ProviderData prepareUiData();
+
+    /** Provider status at various states of the request session. */
+    enum Status {
+        NOT_STARTED,
+        PENDING,
+        REQUIRES_AUTHENTICATION,
+        COMPLETE,
+        SERVICE_DEAD,
+        CANCELED
+    }
+
+    protected void setStatus(@NonNull Status status) {
+        mStatus = status;
+    }
+
+    @NonNull
+    protected Status getStatus() {
+        return mStatus;
+    }
+
+    @NonNull
+    protected ComponentName getComponentName() {
+        return mComponentName;
+    }
+
+    /** Updates the status .*/
+    protected void updateStatusAndInvokeCallback(@NonNull Status status) {
+        setStatus(status);
+        mCallbacks.onProviderStatusChanged(status, mComponentName);
+    }
+
+    @NonNull
+    public static Status toStatus(
+            @CredentialProviderException.CredentialProviderError int errorCode) {
+        // TODO : Add more mappings as more flows are supported
+        return Status.CANCELED;
+    }
+
+    /**
+     * Returns true if the given status means that the provider session must be terminated.
+     */
+    public static boolean isTerminatingStatus(Status status) {
+        return status == Status.CANCELED || status == Status.SERVICE_DEAD;
+    }
+
+    /**
+     * Returns true if the given status means that the provider is done getting the response,
+     * and is ready for user interaction.
+     */
+    public static boolean isCompletionStatus(Status status) {
+        return status == Status.COMPLETE || status == Status.REQUIRES_AUTHENTICATION;
+    }
+}
diff --git a/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
new file mode 100644
index 0000000..d0b6e7d
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/RemoteCredentialService.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.credentials;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Handler;
+import android.os.ICancellationSignal;
+import android.os.RemoteException;
+import android.service.credentials.CredentialProviderException;
+import android.service.credentials.CredentialProviderException.CredentialProviderError;
+import android.service.credentials.CredentialProviderService;
+import android.service.credentials.GetCredentialsRequest;
+import android.service.credentials.GetCredentialsResponse;
+import android.service.credentials.ICredentialProviderService;
+import android.service.credentials.IGetCredentialsCallback;
+import android.text.format.DateUtils;
+import android.util.Log;
+import android.util.Slog;
+
+import com.android.internal.infra.ServiceConnector;
+
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicReference;
+
+/**
+ * Handles connections with the remote credential provider
+ *
+ * @hide
+ */
+public class RemoteCredentialService extends ServiceConnector.Impl<ICredentialProviderService>{
+
+    private static final String TAG = "RemoteCredentialService";
+    /** Timeout for a single request. */
+    private static final long TIMEOUT_REQUEST_MILLIS = 5 * DateUtils.SECOND_IN_MILLIS;
+    /** Timeout to unbind after the task queue is empty. */
+    private static final long TIMEOUT_IDLE_SERVICE_CONNECTION_MILLIS =
+            5 * DateUtils.SECOND_IN_MILLIS;
+
+    private final ComponentName mComponentName;
+
+    /**
+     * Callbacks to be invoked when the provider remote service responds with a
+     * success or failure.
+     * @param <T> the type of response expected from the provider
+     */
+    public interface ProviderCallbacks<T> {
+        /** Called when a successful response is received from the remote provider. */
+        void onProviderResponseSuccess(@Nullable T response);
+        /** Called when a failure response is received from the remote provider. */
+        void onProviderResponseFailure(int errorCode, @Nullable CharSequence message);
+        /** Called when the remote provider service dies. */
+        void onProviderServiceDied(RemoteCredentialService service);
+    }
+
+    public RemoteCredentialService(@NonNull Context context,
+            @NonNull ComponentName componentName, int userId) {
+        super(context, new Intent(CredentialProviderService.SERVICE_INTERFACE)
+                        .setComponent(componentName), Context.BIND_ALLOW_BACKGROUND_ACTIVITY_STARTS,
+                userId, ICredentialProviderService.Stub::asInterface);
+        mComponentName = componentName;
+    }
+
+    /** Unbinds automatically after this amount of time. */
+    @Override
+    protected long getAutoDisconnectTimeoutMs() {
+        return TIMEOUT_IDLE_SERVICE_CONNECTION_MILLIS;
+    }
+
+    /** Return the componentName of the service to be connected. */
+    @NonNull public ComponentName getComponentName() {
+        return mComponentName;
+    }
+
+    /** Destroys this remote service by unbinding the connection. */
+    public void destroy() {
+        unbind();
+    }
+
+    /** Main entry point to be called for executing a getCredential call on the remote
+     * provider service.
+     * @param request the request to be sent to the provider
+     * @param callback the callback to be used to send back the provider response to the
+     *                 {@link ProviderSession} class that maintains provider state
+     */
+    public void onGetCredentials(@NonNull GetCredentialsRequest request,
+            ProviderCallbacks<GetCredentialsResponse> callback) {
+        Log.i(TAG, "In onGetCredentials in RemoteCredentialService");
+        AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
+        AtomicReference<CompletableFuture<GetCredentialsResponse>> futureRef =
+                new AtomicReference<>();
+
+        CompletableFuture<GetCredentialsResponse> connectThenExecute = postAsync(service -> {
+            CompletableFuture<GetCredentialsResponse> getCredentials = new CompletableFuture<>();
+            ICancellationSignal cancellationSignal =
+                    service.onGetCredentials(request, new IGetCredentialsCallback.Stub() {
+                @Override
+                public void onSuccess(GetCredentialsResponse response) {
+                    Log.i(TAG, "In onSuccess in RemoteCredentialService");
+                    getCredentials.complete(response);
+                }
+
+                @Override
+                public void onFailure(@CredentialProviderError int errorCode,
+                        CharSequence message) {
+                    Log.i(TAG, "In onFailure in RemoteCredentialService");
+                    String errorMsg = message == null ? "" : String.valueOf(message);
+                    getCredentials.completeExceptionally(new CredentialProviderException(
+                            errorCode, errorMsg));
+                }
+            });
+            CompletableFuture<GetCredentialsResponse> future = futureRef.get();
+            if (future != null && future.isCancelled()) {
+                dispatchCancellationSignal(cancellationSignal);
+            } else {
+                cancellationSink.set(cancellationSignal);
+            }
+            return getCredentials;
+        }).orTimeout(TIMEOUT_REQUEST_MILLIS, TimeUnit.MILLISECONDS);
+        futureRef.set(connectThenExecute);
+
+        connectThenExecute.whenComplete((result, error) -> Handler.getMain().post(() -> {
+            if (error == null) {
+                Log.i(TAG, "In RemoteCredentialService execute error is null");
+                callback.onProviderResponseSuccess(result);
+            } else {
+                if (error instanceof TimeoutException) {
+                    Log.i(TAG, "In RemoteCredentialService execute error is timeout");
+                    dispatchCancellationSignal(cancellationSink.get());
+                    callback.onProviderResponseFailure(
+                            CredentialProviderException.ERROR_TIMEOUT,
+                            error.getMessage());
+                } else if (error instanceof CancellationException) {
+                    Log.i(TAG, "In RemoteCredentialService execute error is cancellation");
+                    dispatchCancellationSignal(cancellationSink.get());
+                    callback.onProviderResponseFailure(
+                            CredentialProviderException.ERROR_TASK_CANCELED,
+                            error.getMessage());
+                } else if (error instanceof CredentialProviderException) {
+                    Log.i(TAG, "In RemoteCredentialService execute error is provider error");
+                    callback.onProviderResponseFailure(((CredentialProviderException) error)
+                                    .getErrorCode(),
+                            error.getMessage());
+                } else {
+                    Log.i(TAG, "In RemoteCredentialService execute error is unknown");
+                    callback.onProviderResponseFailure(
+                            CredentialProviderException.ERROR_UNKNOWN,
+                            error.getMessage());
+                }
+            }
+        }));
+    }
+
+    private void dispatchCancellationSignal(@Nullable ICancellationSignal signal) {
+        if (signal == null) {
+            Slog.e(TAG, "Error dispatching a cancellation - Signal is null");
+            return;
+        }
+        try {
+            signal.cancel();
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Error dispatching a cancellation", e);
+        }
+    }
+}
diff --git a/services/credentials/java/com/android/server/credentials/RequestSession.java b/services/credentials/java/com/android/server/credentials/RequestSession.java
new file mode 100644
index 0000000..1bacbb3
--- /dev/null
+++ b/services/credentials/java/com/android/server/credentials/RequestSession.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.credentials;
+
+import android.annotation.NonNull;
+import android.annotation.UserIdInt;
+import android.content.ComponentName;
+import android.content.Context;
+import android.credentials.ui.UserSelectionDialogResult;
+import android.os.Binder;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+
+/**
+ * Base class of a request session, that listens to UI events. This class must be extended
+ * every time a new response type is expected from the providers.
+ */
+abstract class RequestSession implements CredentialManagerUi.CredentialManagerUiCallback,
+        ProviderSession.ProviderInternalCallback {
+    @NonNull protected final IBinder mRequestId;
+    @NonNull protected final Context mContext;
+    @NonNull protected final CredentialManagerUi mCredentialManagerUi;
+    @NonNull protected final String mRequestType;
+    @NonNull protected final Handler mHandler;
+    @NonNull protected boolean mIsFirstUiTurn = true;
+    @UserIdInt protected final int mUserId;
+
+    protected RequestSession(@NonNull Context context,
+            @UserIdInt int userId, @NonNull String requestType) {
+        mContext = context;
+        mUserId = userId;
+        mRequestType = requestType;
+        mHandler = new Handler(Looper.getMainLooper(), null, true);
+        mRequestId = new Binder();
+        mCredentialManagerUi = new CredentialManagerUi(mContext,
+                mUserId, this);
+    }
+
+    /** Returns the unique identifier of this request session. */
+    public IBinder getRequestId() {
+        return mRequestId;
+    }
+
+    @Override // from CredentialManagerUiCallback
+    public abstract void onUiSelection(UserSelectionDialogResult selection);
+
+    @Override // from CredentialManagerUiCallback
+    public abstract void onUiCancelation();
+
+    @Override // from ProviderInternalCallback
+    public abstract void onProviderStatusChanged(ProviderSession.Status status, ComponentName componentName);
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index b74fedf..593e648 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -154,6 +154,7 @@
 import com.android.server.people.PeopleService;
 import com.android.server.pm.ApexManager;
 import com.android.server.pm.ApexSystemServiceInfo;
+import com.android.server.pm.BackgroundInstallControlService;
 import com.android.server.pm.CrossProfileAppsService;
 import com.android.server.pm.DataLoaderManagerService;
 import com.android.server.pm.DynamicCodeLoggingService;
@@ -2469,6 +2470,10 @@
             t.traceBegin("StartMediaMetricsManager");
             mSystemServiceManager.startService(MediaMetricsManagerService.class);
             t.traceEnd();
+
+            t.traceBegin("StartBackgroundInstallControlService");
+            mSystemServiceManager.startService(BackgroundInstallControlService.class);
+            t.traceEnd();
         }
 
         t.traceBegin("StartMediaProjectionManager");
diff --git a/services/print/java/com/android/server/print/PrintManagerService.java b/services/print/java/com/android/server/print/PrintManagerService.java
index 66524edf..35b9bc3 100644
--- a/services/print/java/com/android/server/print/PrintManagerService.java
+++ b/services/print/java/com/android/server/print/PrintManagerService.java
@@ -984,6 +984,7 @@
             monitor.register(mContext, BackgroundThread.getHandler().getLooper(),
                     UserHandle.ALL, true);
         }
+
         private UserState getOrCreateUserStateLocked(int userId, boolean lowPriority) {
             return getOrCreateUserStateLocked(userId, lowPriority,
                     true /* enforceUserUnlockingOrUnlocked */);
@@ -991,6 +992,12 @@
 
         private UserState getOrCreateUserStateLocked(int userId, boolean lowPriority,
                 boolean enforceUserUnlockingOrUnlocked) {
+            return getOrCreateUserStateLocked(userId, lowPriority,
+                    enforceUserUnlockingOrUnlocked, false /* shouldUpdateState */);
+        }
+
+        private UserState getOrCreateUserStateLocked(int userId, boolean lowPriority,
+                boolean enforceUserUnlockingOrUnlocked, boolean shouldUpdateState) {
             if (enforceUserUnlockingOrUnlocked && !mUserManager.isUserUnlockingOrUnlocked(userId)) {
                 throw new IllegalStateException(
                         "User " + userId + " must be unlocked for printing to be available");
@@ -1000,6 +1007,8 @@
             if (userState == null) {
                 userState = new UserState(mContext, userId, mLock, lowPriority);
                 mUserStates.put(userId, userState);
+            } else if (shouldUpdateState) {
+                userState.updateIfNeededLocked();
             }
 
             if (!lowPriority) {
@@ -1019,9 +1028,9 @@
 
                     UserState userState;
                     synchronized (mLock) {
-                        userState = getOrCreateUserStateLocked(userId, true,
-                                false /*enforceUserUnlockingOrUnlocked */);
-                        userState.updateIfNeededLocked();
+                        userState = getOrCreateUserStateLocked(userId, /* lowPriority */ true,
+                                /* enforceUserUnlockingOrUnlocked */ false,
+                                /* shouldUpdateState */ true);
                     }
                     // This is the first time we switch to this user after boot, so
                     // now is the time to remove obsolete print jobs since they
diff --git a/services/tests/InputMethodSystemServerTests/Android.bp b/services/tests/InputMethodSystemServerTests/Android.bp
new file mode 100644
index 0000000..939fb6a
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/Android.bp
@@ -0,0 +1,62 @@
+// Copyright (C) 2022 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+    name: "FrameworksInputMethodSystemServerTests",
+    defaults: [
+        "modules-utils-testable-device-config-defaults",
+    ],
+
+    srcs: [
+        "src/**/*.java",
+    ],
+
+    static_libs: [
+        "androidx.test.core",
+        "androidx.test.runner",
+        "androidx.test.espresso.core",
+        "androidx.test.espresso.contrib",
+        "androidx.test.ext.truth",
+        "frameworks-base-testutils",
+        "mockito-target-extended-minus-junit4",
+        "platform-test-annotations",
+        "services.core",
+        "servicestests-core-utils",
+        "servicestests-utils-mockito-extended",
+        "truth-prebuilt",
+    ],
+
+    libs: [
+        "android.test.mock",
+        "android.test.base",
+        "android.test.runner",
+    ],
+
+    certificate: "platform",
+    platform_apis: true,
+    test_suites: ["device-tests"],
+
+    optimize: {
+        enabled: false,
+    },
+}
diff --git a/services/tests/InputMethodSystemServerTests/AndroidManifest.xml b/services/tests/InputMethodSystemServerTests/AndroidManifest.xml
new file mode 100644
index 0000000..12e7cfc
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/AndroidManifest.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 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.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.frameworks.inputmethodtests">
+
+    <uses-sdk android:targetSdkVersion="31" />
+
+    <!-- Permissions required for granting and logging -->
+    <uses-permission android:name="android.permission.LOG_COMPAT_CHANGE"/>
+    <uses-permission android:name="android.permission.READ_COMPAT_CHANGE_CONFIG"/>
+    <uses-permission android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG"/>
+    <uses-permission android:name="android.permission.OVERRIDE_COMPAT_CHANGE_CONFIG_ON_RELEASE_BUILD"/>
+
+    <!-- Permissions for reading system info -->
+    <uses-permission android:name="android.permission.READ_DEVICE_CONFIG" />
+    <uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL" />
+
+    <application android:testOnly="true"
+                 android:debuggable="true">
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation
+        android:name="androidx.test.runner.AndroidJUnitRunner"
+        android:targetPackage="com.android.frameworks.inputmethodtests"
+        android:label="Frameworks InputMethod System Service Tests" />
+
+</manifest>
diff --git a/services/tests/InputMethodSystemServerTests/AndroidTest.xml b/services/tests/InputMethodSystemServerTests/AndroidTest.xml
new file mode 100644
index 0000000..92be780
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/AndroidTest.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2022 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.
+-->
+<configuration description="Runs Frameworks InputMethod System Services Tests.">
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="apct-instrumentation" />
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="install-arg" value="-t" />
+        <option name="test-file-name" value="FrameworksInputMethodSystemServerTests.apk" />
+    </target_preparer>
+
+    <option name="test-tag" value="FrameworksInputMethodSystemServerTests" />
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.frameworks.inputmethodtests" />
+        <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
+        <option name="hidden-api-checks" value="false"/>
+    </test>
+
+    <!-- Collect the files in the dump directory for debugging -->
+    <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+        <option name="directory-keys" value="/sdcard/FrameworksInputMethodSystemServerTests/" />
+        <option name="collect-on-run-ended-only" value="true" />
+    </metrics_collector>
+</configuration>
diff --git a/services/tests/InputMethodSystemServerTests/OWNERS b/services/tests/InputMethodSystemServerTests/OWNERS
new file mode 100644
index 0000000..1f2c036
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/inputmethod/OWNERS
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
new file mode 100644
index 0000000..3fbc400
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceTestBase.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import android.app.ActivityManagerInternal;
+import android.content.Context;
+import android.content.pm.PackageManagerInternal;
+import android.hardware.display.DisplayManagerInternal;
+import android.hardware.input.IInputManager;
+import android.hardware.input.InputManager;
+import android.os.Binder;
+import android.os.IBinder;
+import android.os.Process;
+import android.os.RemoteException;
+import android.os.ServiceManager;
+import android.os.UserHandle;
+import android.view.inputmethod.EditorInfo;
+import android.window.ImeOnBackInvokedDispatcher;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import com.android.internal.compat.IPlatformCompat;
+import com.android.internal.inputmethod.IInputMethod;
+import com.android.internal.inputmethod.IInputMethodClient;
+import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
+import com.android.internal.inputmethod.IRemoteInputConnection;
+import com.android.internal.inputmethod.InputBindResult;
+import com.android.internal.view.IInputMethodManager;
+import com.android.server.LocalServices;
+import com.android.server.ServiceThread;
+import com.android.server.SystemServerInitThreadPool;
+import com.android.server.SystemService;
+import com.android.server.input.InputManagerInternal;
+import com.android.server.pm.UserManagerInternal;
+import com.android.server.wm.WindowManagerInternal;
+
+import org.junit.After;
+import org.junit.Before;
+import org.mockito.Mock;
+import org.mockito.MockitoSession;
+import org.mockito.quality.Strictness;
+
+/** Base class for testing {@link InputMethodManagerService}. */
+public class InputMethodManagerServiceTestBase {
+    protected static final String TEST_SELECTED_IME_ID = "test.ime";
+    protected static final String TEST_EDITOR_PKG_NAME = "test.editor";
+    protected static final String TEST_FOCUSED_WINDOW_NAME = "test.editor/activity";
+    protected static final WindowManagerInternal.ImeTargetInfo TEST_IME_TARGET_INFO =
+            new WindowManagerInternal.ImeTargetInfo(
+                    TEST_FOCUSED_WINDOW_NAME,
+                    TEST_FOCUSED_WINDOW_NAME,
+                    TEST_FOCUSED_WINDOW_NAME,
+                    TEST_FOCUSED_WINDOW_NAME,
+                    TEST_FOCUSED_WINDOW_NAME);
+    protected static final InputBindResult SUCCESS_WAITING_IME_BINDING_RESULT =
+            new InputBindResult(
+                    InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING,
+                    null,
+                    null,
+                    null,
+                    "0",
+                    0,
+                    null,
+                    false);
+
+    @Mock protected WindowManagerInternal mMockWindowManagerInternal;
+    @Mock protected ActivityManagerInternal mMockActivityManagerInternal;
+    @Mock protected PackageManagerInternal mMockPackageManagerInternal;
+    @Mock protected InputManagerInternal mMockInputManagerInternal;
+    @Mock protected DisplayManagerInternal mMockDisplayManagerInternal;
+    @Mock protected UserManagerInternal mMockUserManagerInternal;
+    @Mock protected InputMethodBindingController mMockInputMethodBindingController;
+    @Mock protected IInputMethodClient mMockInputMethodClient;
+    @Mock protected IBinder mWindowToken;
+    @Mock protected IRemoteInputConnection mMockRemoteInputConnection;
+    @Mock protected IRemoteAccessibilityInputConnection mMockRemoteAccessibilityInputConnection;
+    @Mock protected ImeOnBackInvokedDispatcher mMockImeOnBackInvokedDispatcher;
+    @Mock protected IInputMethodManager.Stub mMockIInputMethodManager;
+    @Mock protected IPlatformCompat.Stub mMockIPlatformCompat;
+    @Mock protected IInputMethod mMockInputMethod;
+    @Mock protected IBinder mMockInputMethodBinder;
+    @Mock protected IInputManager mMockIInputManager;
+
+    protected Context mContext;
+    protected MockitoSession mMockingSession;
+    protected int mTargetSdkVersion;
+    protected int mCallingUserId;
+    protected EditorInfo mEditorInfo;
+    protected IInputMethodInvoker mMockInputMethodInvoker;
+    protected InputMethodManagerService mInputMethodManagerService;
+    protected ServiceThread mServiceThread;
+
+    @Before
+    public void setUp() throws RemoteException {
+        mMockingSession =
+                mockitoSession()
+                        .initMocks(this)
+                        .strictness(Strictness.LENIENT)
+                        .mockStatic(LocalServices.class)
+                        .mockStatic(ServiceManager.class)
+                        .mockStatic(SystemServerInitThreadPool.class)
+                        .startMocking();
+
+        mContext = InstrumentationRegistry.getInstrumentation().getContext();
+        spyOn(mContext);
+
+        mTargetSdkVersion = mContext.getApplicationInfo().targetSdkVersion;
+        mCallingUserId = UserHandle.getCallingUserId();
+        mEditorInfo = new EditorInfo();
+        mEditorInfo.packageName = TEST_EDITOR_PKG_NAME;
+
+        // Injecting and mocking local services.
+        doReturn(mMockWindowManagerInternal)
+                .when(() -> LocalServices.getService(WindowManagerInternal.class));
+        doReturn(mMockActivityManagerInternal)
+                .when(() -> LocalServices.getService(ActivityManagerInternal.class));
+        doReturn(mMockPackageManagerInternal)
+                .when(() -> LocalServices.getService(PackageManagerInternal.class));
+        doReturn(mMockInputManagerInternal)
+                .when(() -> LocalServices.getService(InputManagerInternal.class));
+        doReturn(mMockDisplayManagerInternal)
+                .when(() -> LocalServices.getService(DisplayManagerInternal.class));
+        doReturn(mMockUserManagerInternal)
+                .when(() -> LocalServices.getService(UserManagerInternal.class));
+        doReturn(mMockIInputMethodManager)
+                .when(() -> ServiceManager.getServiceOrThrow(Context.INPUT_METHOD_SERVICE));
+        doReturn(mMockIPlatformCompat)
+                .when(() -> ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
+
+        // Stubbing out context related methods to avoid the system holding strong references to
+        // InputMethodManagerService.
+        doNothing().when(mContext).enforceCallingPermission(anyString(), anyString());
+        doNothing().when(mContext).sendBroadcastAsUser(any(), any());
+        doReturn(null).when(mContext).registerReceiver(any(), any());
+        doReturn(null)
+                .when(mContext)
+                .registerReceiverAsUser(any(), any(), any(), anyString(), any(), anyInt());
+
+        // Injecting and mocked InputMethodBindingController and InputMethod.
+        mMockInputMethodInvoker = IInputMethodInvoker.create(mMockInputMethod);
+        InputManager.resetInstance(mMockIInputManager);
+        synchronized (ImfLock.class) {
+            when(mMockInputMethodBindingController.getCurMethod())
+                    .thenReturn(mMockInputMethodInvoker);
+            when(mMockInputMethodBindingController.bindCurrentMethod())
+                    .thenReturn(SUCCESS_WAITING_IME_BINDING_RESULT);
+            doNothing().when(mMockInputMethodBindingController).unbindCurrentMethod();
+            when(mMockInputMethodBindingController.getSelectedMethodId())
+                    .thenReturn(TEST_SELECTED_IME_ID);
+        }
+
+        // Shuffling around all other initialization to make the test runnable.
+        when(mMockIInputManager.getInputDeviceIds()).thenReturn(new int[0]);
+        when(mMockIInputMethodManager.isImeTraceEnabled()).thenReturn(false);
+        when(mMockIPlatformCompat.isChangeEnabledByUid(anyLong(), anyInt())).thenReturn(true);
+        when(mMockUserManagerInternal.isUserRunning(anyInt())).thenReturn(true);
+        when(mMockUserManagerInternal.getProfileIds(anyInt(), anyBoolean()))
+                .thenReturn(new int[] {0});
+        when(mMockActivityManagerInternal.isSystemReady()).thenReturn(true);
+        when(mMockPackageManagerInternal.getPackageUid(anyString(), anyLong(), anyInt()))
+                .thenReturn(Binder.getCallingUid());
+        when(mMockWindowManagerInternal.onToggleImeRequested(anyBoolean(), any(), any(), anyInt()))
+                .thenReturn(TEST_IME_TARGET_INFO);
+        when(mMockInputMethodClient.asBinder()).thenReturn(mMockInputMethodBinder);
+
+        // Used by lazy initializing draw IMS nav bar at InputMethodManagerService#systemRunning(),
+        // which is ok to be mocked out for now.
+        doReturn(null).when(() -> SystemServerInitThreadPool.submit(any(), anyString()));
+
+        mServiceThread =
+                new ServiceThread(
+                        "TestServiceThread",
+                        Process.THREAD_PRIORITY_FOREGROUND, /* allowIo */
+                        false);
+        mInputMethodManagerService =
+                new InputMethodManagerService(
+                        mContext, mServiceThread, mMockInputMethodBindingController);
+
+        // Start a InputMethodManagerService.Lifecycle to publish and manage the lifecycle of
+        // InputMethodManagerService, which is closer to the real situation.
+        InputMethodManagerService.Lifecycle lifecycle =
+                new InputMethodManagerService.Lifecycle(mContext, mInputMethodManagerService);
+
+        // Public local InputMethodManagerService.
+        lifecycle.onStart();
+        try {
+            // After this boot phase, services can broadcast Intents.
+            lifecycle.onBootPhase(SystemService.PHASE_ACTIVITY_MANAGER_READY);
+        } catch (SecurityException e) {
+            // Security exception to permission denial is expected in test, mocking out to ensure
+            // InputMethodManagerService as system ready state.
+            if (!e.getMessage().contains("Permission Denial: not allowed to send broadcast")) {
+                throw e;
+            }
+        }
+
+        // Call InputMethodManagerService#addClient() as a preparation to start interacting with it.
+        mInputMethodManagerService.addClient(mMockInputMethodClient, mMockRemoteInputConnection, 0);
+    }
+
+    @After
+    public void tearDown() {
+        if (mServiceThread != null) {
+            mServiceThread.quitSafely();
+        }
+
+        if (mMockingSession != null) {
+            mMockingSession.finishMocking();
+        }
+    }
+
+    protected void verifyShowSoftInput(boolean setVisible, boolean showSoftInput)
+            throws RemoteException {
+        synchronized (ImfLock.class) {
+            verify(mMockInputMethodBindingController, times(setVisible ? 1 : 0))
+                    .setCurrentMethodVisible();
+        }
+        verify(mMockInputMethod, times(showSoftInput ? 1 : 0))
+                .showSoftInput(any(), anyInt(), any());
+    }
+
+    protected void verifyHideSoftInput(boolean setNotVisible, boolean hideSoftInput)
+            throws RemoteException {
+        synchronized (ImfLock.class) {
+            verify(mMockInputMethodBindingController, times(setNotVisible ? 1 : 0))
+                    .setCurrentMethodNotVisible();
+        }
+        verify(mMockInputMethod, times(hideSoftInput ? 1 : 0))
+                .hideSoftInput(any(), anyInt(), any());
+    }
+}
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceWindowGainedFocusTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceWindowGainedFocusTest.java
new file mode 100644
index 0000000..ffa2729
--- /dev/null
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodManagerServiceWindowGainedFocusTest.java
@@ -0,0 +1,266 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.inputmethod;
+
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_NOTHING;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNCHANGED;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED;
+import static android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.when;
+
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.inputmethod.EditorInfo;
+import android.window.ImeOnBackInvokedDispatcher;
+
+import com.android.internal.inputmethod.IInputMethodClient;
+import com.android.internal.inputmethod.IRemoteAccessibilityInputConnection;
+import com.android.internal.inputmethod.IRemoteInputConnection;
+import com.android.internal.inputmethod.InputBindResult;
+import com.android.internal.inputmethod.InputMethodDebug;
+import com.android.internal.inputmethod.StartInputFlags;
+import com.android.internal.inputmethod.StartInputReason;
+import com.android.server.wm.WindowManagerInternal;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Test the behavior of {@link InputMethodManagerService#startInputOrWindowGainedFocus(int,
+ * IInputMethodClient, IBinder, int, int, int, EditorInfo, IRemoteInputConnection,
+ * IRemoteAccessibilityInputConnection, int, int, ImeOnBackInvokedDispatcher)}.
+ */
+@RunWith(Parameterized.class)
+public class InputMethodManagerServiceWindowGainedFocusTest
+        extends InputMethodManagerServiceTestBase {
+    private static final String TAG = "IMMSWindowGainedFocusTest";
+
+    private static final int[] SOFT_INPUT_STATE_FLAGS =
+            new int[] {
+                SOFT_INPUT_STATE_UNSPECIFIED,
+                SOFT_INPUT_STATE_UNCHANGED,
+                SOFT_INPUT_STATE_HIDDEN,
+                SOFT_INPUT_STATE_ALWAYS_HIDDEN,
+                SOFT_INPUT_STATE_VISIBLE,
+                SOFT_INPUT_STATE_ALWAYS_VISIBLE
+            };
+    private static final int[] SOFT_INPUT_ADJUST_FLAGS =
+            new int[] {
+                SOFT_INPUT_ADJUST_UNSPECIFIED,
+                SOFT_INPUT_ADJUST_RESIZE,
+                SOFT_INPUT_ADJUST_PAN,
+                SOFT_INPUT_ADJUST_NOTHING
+            };
+    private static final int DEFAULT_SOFT_INPUT_FLAG =
+            StartInputFlags.VIEW_HAS_FOCUS | StartInputFlags.IS_TEXT_EDITOR;
+
+    @Parameterized.Parameters(name = "softInputState={0}, softInputAdjustment={1}")
+    public static List<Object[]> softInputModeConfigs() {
+        ArrayList<Object[]> params = new ArrayList<>();
+        for (int softInputState : SOFT_INPUT_STATE_FLAGS) {
+            for (int softInputAdjust : SOFT_INPUT_ADJUST_FLAGS) {
+                params.add(new Object[] {softInputState, softInputAdjust});
+            }
+        }
+        return params;
+    }
+
+    private final int mSoftInputState;
+    private final int mSoftInputAdjustment;
+
+    public InputMethodManagerServiceWindowGainedFocusTest(
+            int softInputState, int softInputAdjustment) {
+        mSoftInputState = softInputState;
+        mSoftInputAdjustment = softInputAdjustment;
+    }
+
+    @Test
+    public void startInputOrWindowGainedFocus_forwardNavigation() throws RemoteException {
+        mockHasImeFocusAndRestoreImeVisibility(false /* restoreImeVisibility */);
+
+        assertThat(
+                        startInputOrWindowGainedFocus(
+                                DEFAULT_SOFT_INPUT_FLAG, true /* forwardNavigation */))
+                .isEqualTo(SUCCESS_WAITING_IME_BINDING_RESULT);
+
+        switch (mSoftInputState) {
+            case SOFT_INPUT_STATE_UNSPECIFIED:
+                boolean showSoftInput = mSoftInputAdjustment == SOFT_INPUT_ADJUST_RESIZE;
+                verifyShowSoftInput(
+                        showSoftInput /* setVisible */, showSoftInput /* showSoftInput */);
+                // Soft input was hidden by default, so it doesn't need to call
+                // {@code IMS#hideSoftInput()}.
+                verifyHideSoftInput(!showSoftInput /* setNotVisible */, false /* hideSoftInput */);
+                break;
+            case SOFT_INPUT_STATE_VISIBLE:
+            case SOFT_INPUT_STATE_ALWAYS_VISIBLE:
+                verifyShowSoftInput(true /* setVisible */, true /* showSoftInput */);
+                verifyHideSoftInput(false /* setNotVisible */, false /* hideSoftInput */);
+                break;
+            case SOFT_INPUT_STATE_UNCHANGED: // Do nothing
+                verifyShowSoftInput(false /* setVisible */, false /* showSoftInput */);
+                verifyHideSoftInput(false /* setNotVisible */, false /* hideSoftInput */);
+                break;
+            case SOFT_INPUT_STATE_HIDDEN:
+            case SOFT_INPUT_STATE_ALWAYS_HIDDEN:
+                verifyShowSoftInput(false /* setVisible */, false /* showSoftInput */);
+                // Soft input was hidden by default, so it doesn't need to call
+                // {@code IMS#hideSoftInput()}.
+                verifyHideSoftInput(true /* setNotVisible */, false /* hideSoftInput */);
+                break;
+            default:
+                throw new IllegalStateException(
+                        "Unhandled soft input mode: "
+                                + InputMethodDebug.softInputModeToString(mSoftInputState));
+        }
+    }
+
+    @Test
+    public void startInputOrWindowGainedFocus_notForwardNavigation() throws RemoteException {
+        mockHasImeFocusAndRestoreImeVisibility(false /* restoreImeVisibility */);
+
+        assertThat(
+                        startInputOrWindowGainedFocus(
+                                DEFAULT_SOFT_INPUT_FLAG, false /* forwardNavigation */))
+                .isEqualTo(SUCCESS_WAITING_IME_BINDING_RESULT);
+
+        switch (mSoftInputState) {
+            case SOFT_INPUT_STATE_UNSPECIFIED:
+                boolean hideSoftInput = mSoftInputAdjustment != SOFT_INPUT_ADJUST_RESIZE;
+                verifyShowSoftInput(false /* setVisible */, false /* showSoftInput */);
+                // Soft input was hidden by default, so it doesn't need to call
+                // {@code IMS#hideSoftInput()}.
+                verifyHideSoftInput(hideSoftInput /* setNotVisible */, false /* hideSoftInput */);
+                break;
+            case SOFT_INPUT_STATE_VISIBLE:
+            case SOFT_INPUT_STATE_HIDDEN:
+            case SOFT_INPUT_STATE_UNCHANGED: // Do nothing
+                verifyShowSoftInput(false /* setVisible */, false /* showSoftInput */);
+                verifyHideSoftInput(false /* setNotVisible */, false /* hideSoftInput */);
+                break;
+            case SOFT_INPUT_STATE_ALWAYS_VISIBLE:
+                verifyShowSoftInput(true /* setVisible */, true /* showSoftInput */);
+                verifyHideSoftInput(false /* setNotVisible */, false /* hideSoftInput */);
+                break;
+            case SOFT_INPUT_STATE_ALWAYS_HIDDEN:
+                verifyShowSoftInput(false /* setVisible */, false /* showSoftInput */);
+                // Soft input was hidden by default, so it doesn't need to call
+                // {@code IMS#hideSoftInput()}.
+                verifyHideSoftInput(true /* setNotVisible */, false /* hideSoftInput */);
+                break;
+            default:
+                throw new IllegalStateException(
+                        "Unhandled soft input mode: "
+                                + InputMethodDebug.softInputModeToString(mSoftInputState));
+        }
+    }
+
+    @Test
+    public void startInputOrWindowGainedFocus_userNotRunning() throws RemoteException {
+        when(mMockUserManagerInternal.isUserRunning(anyInt())).thenReturn(false);
+
+        assertThat(
+                        startInputOrWindowGainedFocus(
+                                DEFAULT_SOFT_INPUT_FLAG, true /* forwardNavigation */))
+                .isEqualTo(InputBindResult.INVALID_USER);
+        verifyShowSoftInput(false /* setVisible */, false /* showSoftInput */);
+        verifyHideSoftInput(false /* setNotVisible */, false /* hideSoftInput */);
+    }
+
+    @Test
+    public void startInputOrWindowGainedFocus_invalidFocusStatus() throws RemoteException {
+        int[] invalidImeClientFocus =
+                new int[] {
+                    WindowManagerInternal.ImeClientFocusResult.NOT_IME_TARGET_WINDOW,
+                    WindowManagerInternal.ImeClientFocusResult.DISPLAY_ID_MISMATCH,
+                    WindowManagerInternal.ImeClientFocusResult.INVALID_DISPLAY_ID
+                };
+        InputBindResult[] inputBingResult =
+                new InputBindResult[] {
+                    InputBindResult.NOT_IME_TARGET_WINDOW,
+                    InputBindResult.DISPLAY_ID_MISMATCH,
+                    InputBindResult.INVALID_DISPLAY_ID
+                };
+
+        for (int i = 0; i < invalidImeClientFocus.length; i++) {
+            when(mMockWindowManagerInternal.hasInputMethodClientFocus(
+                            any(), anyInt(), anyInt(), anyInt()))
+                    .thenReturn(invalidImeClientFocus[i]);
+
+            assertThat(
+                            startInputOrWindowGainedFocus(
+                                    DEFAULT_SOFT_INPUT_FLAG, true /* forwardNavigation */))
+                    .isEqualTo(inputBingResult[i]);
+            verifyShowSoftInput(false /* setVisible */, false /* showSoftInput */);
+            verifyHideSoftInput(false /* setNotVisible */, false /* hideSoftInput */);
+        }
+    }
+
+    private InputBindResult startInputOrWindowGainedFocus(
+            int startInputFlag, boolean forwardNavigation) {
+        int softInputMode = mSoftInputState | mSoftInputAdjustment;
+        if (forwardNavigation) {
+            softInputMode |= SOFT_INPUT_IS_FORWARD_NAVIGATION;
+        }
+
+        Log.i(
+                TAG,
+                "startInputOrWindowGainedFocus() softInputStateFlag="
+                        + InputMethodDebug.softInputModeToString(mSoftInputState)
+                        + ", softInputAdjustFlag="
+                        + InputMethodDebug.softInputModeToString(mSoftInputAdjustment));
+
+        return mInputMethodManagerService.startInputOrWindowGainedFocus(
+                StartInputReason.WINDOW_FOCUS_GAIN /* startInputReason */,
+                mMockInputMethodClient /* client */,
+                mWindowToken /* windowToken */,
+                startInputFlag /* startInputFlags */,
+                softInputMode /* softInputMode */,
+                0 /* windowFlags */,
+                mEditorInfo /* editorInfo */,
+                mMockRemoteInputConnection /* inputConnection */,
+                mMockRemoteAccessibilityInputConnection /* remoteAccessibilityInputConnection */,
+                mTargetSdkVersion /* unverifiedTargetSdkVersion */,
+                mCallingUserId /* userId */,
+                mMockImeOnBackInvokedDispatcher /* imeDispatcher */);
+    }
+
+    private void mockHasImeFocusAndRestoreImeVisibility(boolean restoreImeVisibility) {
+        when(mMockWindowManagerInternal.hasInputMethodClientFocus(
+                        any(), anyInt(), anyInt(), anyInt()))
+                .thenReturn(WindowManagerInternal.ImeClientFocusResult.HAS_IME_FOCUS);
+        when(mMockWindowManagerInternal.shouldRestoreImeVisibility(any()))
+                .thenReturn(restoreImeVisibility);
+    }
+}
diff --git a/services/tests/mockingservicestests/Android.bp b/services/tests/mockingservicestests/Android.bp
index 16317fe..73b1907c 100644
--- a/services/tests/mockingservicestests/Android.bp
+++ b/services/tests/mockingservicestests/Android.bp
@@ -78,6 +78,12 @@
         "servicestests-core-utils",
     ],
 
+    java_resources: [
+        ":apex.test",
+        ":test.rebootless_apex_v1",
+        ":test.rebootless_apex_v2",
+    ],
+
     jni_libs: [
         "libpsi",
     ],
diff --git a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
index cb14864..2583f44 100644
--- a/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/DeviceIdleControllerTest.java
@@ -325,7 +325,7 @@
         doNothing().when(mAlarmManager).set(anyInt(), anyLong(), anyString(), any(), any());
         doNothing().when(mAlarmManager).setExact(anyInt(), anyLong(), anyString(), any(), any());
         doNothing().when(mAlarmManager)
-                .setWindow(anyInt(), anyLong(), anyLong(), anyString(), any(), any());
+                .setWindow(anyInt(), anyLong(), anyLong(), anyString(), any(), any(Handler.class));
         doReturn(mock(Sensor.class)).when(mSensorManager)
                 .getDefaultSensor(eq(Sensor.TYPE_SIGNIFICANT_MOTION), eq(true));
         doReturn(true).when(mSensorManager).registerListener(any(), any(), anyInt());
@@ -1111,12 +1111,12 @@
         alarmManagerInOrder.verify(mAlarmManager).setWindow(
                 eq(AlarmManager.ELAPSED_REALTIME),
                 eq(idleAfterInactiveExpiryTime),
-                anyLong(), anyString(), any(), any());
+                anyLong(), anyString(), any(), any(Handler.class));
         // Maintenance alarm
         alarmManagerInOrder.verify(mAlarmManager).setWindow(
                 eq(AlarmManager.ELAPSED_REALTIME_WAKEUP),
                 eq(idleAfterInactiveExpiryTime + idlingTimeMs),
-                anyLong(), anyString(), any(), any());
+                anyLong(), anyString(), any(), any(Handler.class));
 
         final AlarmManager.OnAlarmListener progressionListener =
                 alarmListenerCaptor.getAllValues().get(0);
@@ -1130,7 +1130,7 @@
         alarmManagerInOrder.verify(mAlarmManager).setWindow(
                 eq(AlarmManager.ELAPSED_REALTIME_WAKEUP),
                 eq(mInjector.nowElapsed + idlingTimeMs),
-                anyLong(), anyString(), any(), any());
+                anyLong(), anyString(), any(), any(Handler.class));
 
         for (int i = 0; i < 2; ++i) {
             // IDLE->MAINTENANCE alarm
@@ -1144,12 +1144,12 @@
             alarmManagerInOrder.verify(mAlarmManager).setWindow(
                     eq(AlarmManager.ELAPSED_REALTIME),
                     eq(maintenanceExpiryTime),
-                    anyLong(), anyString(), any(), any());
+                    anyLong(), anyString(), any(), any(Handler.class));
             // Set IDLE->MAINTENANCE
             alarmManagerInOrder.verify(mAlarmManager).setWindow(
                     eq(AlarmManager.ELAPSED_REALTIME_WAKEUP),
                     eq(maintenanceExpiryTime + idlingTimeMs),
-                    anyLong(), anyString(), any(), any());
+                    anyLong(), anyString(), any(), any(Handler.class));
 
             // MAINTENANCE->IDLE alarm
             mInjector.nowElapsed = mDeviceIdleController.getNextLightAlarmTimeForTesting();
@@ -1159,7 +1159,7 @@
             alarmManagerInOrder.verify(mAlarmManager).setWindow(
                     eq(AlarmManager.ELAPSED_REALTIME_WAKEUP),
                     eq(mInjector.nowElapsed + idlingTimeMs),
-                    anyLong(), anyString(), any(), any());
+                    anyLong(), anyString(), any(), any(Handler.class));
         }
     }
 
@@ -2019,7 +2019,8 @@
         final ArgumentCaptor<AlarmManager.OnAlarmListener> alarmListener = ArgumentCaptor
                 .forClass(AlarmManager.OnAlarmListener.class);
         doNothing().when(mAlarmManager).setWindow(
-                anyInt(), anyLong(), anyLong(), eq("DeviceIdleController.motion"), any(), any());
+                anyInt(), anyLong(), anyLong(), eq("DeviceIdleController.motion"), any(),
+                any(Handler.class));
         doNothing().when(mAlarmManager).setWindow(anyInt(), anyLong(), anyLong(),
                 eq("DeviceIdleController.motion_registration"),
                 alarmListener.capture(), any());
@@ -2063,7 +2064,8 @@
         final ArgumentCaptor<AlarmManager.OnAlarmListener> alarmListener = ArgumentCaptor
                 .forClass(AlarmManager.OnAlarmListener.class);
         doNothing().when(mAlarmManager).setWindow(
-                anyInt(), anyLong(), anyLong(), eq("DeviceIdleController.motion"), any(), any());
+                anyInt(), anyLong(), anyLong(), eq("DeviceIdleController.motion"), any(),
+                any(Handler.class));
         doNothing().when(mAlarmManager).setWindow(anyInt(), anyLong(), anyLong(),
                 eq("DeviceIdleController.motion_registration"),
                 alarmListener.capture(), any());
@@ -2130,7 +2132,7 @@
                         eq(SensorManager.SENSOR_DELAY_NORMAL));
         inOrder.verify(mAlarmManager).setWindow(
                 anyInt(), eq(mInjector.nowElapsed + mConstants.MOTION_INACTIVE_TIMEOUT), anyLong(),
-                eq("DeviceIdleController.motion"), any(), any());
+                eq("DeviceIdleController.motion"), any(), any(Handler.class));
         final SensorEventListener listener = listenerCaptor.getValue();
 
         // Trigger motion
@@ -2140,7 +2142,7 @@
         final ArgumentCaptor<Long> registrationTimeCaptor = ArgumentCaptor.forClass(Long.class);
         inOrder.verify(mAlarmManager).setWindow(
                 anyInt(), registrationTimeCaptor.capture(), anyLong(),
-                eq("DeviceIdleController.motion_registration"), any(), any());
+                eq("DeviceIdleController.motion_registration"), any(), any(Handler.class));
 
         // Make sure the listener is re-registered.
         mInjector.nowElapsed = registrationTimeCaptor.getValue();
@@ -2150,7 +2152,7 @@
                         eq(SensorManager.SENSOR_DELAY_NORMAL));
         final ArgumentCaptor<Long> timeoutCaptor = ArgumentCaptor.forClass(Long.class);
         inOrder.verify(mAlarmManager).setWindow(anyInt(), timeoutCaptor.capture(), anyLong(),
-                eq("DeviceIdleController.motion"), any(), any());
+                eq("DeviceIdleController.motion"), any(), any(Handler.class));
 
         // No motion before timeout
         stationaryListener.motionExpected = false;
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
index 5b7b8f4..4d92b7f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueModernImplTest.java
@@ -25,6 +25,7 @@
 import static com.android.server.am.BroadcastQueueTest.PACKAGE_YELLOW;
 import static com.android.server.am.BroadcastQueueTest.getUidForPackage;
 import static com.android.server.am.BroadcastQueueTest.makeManifestReceiver;
+import static com.android.server.am.BroadcastQueueTest.withPriority;
 
 import static com.google.common.truth.Truth.assertThat;
 
@@ -46,8 +47,10 @@
 import android.os.Bundle;
 import android.os.BundleMerger;
 import android.os.HandlerThread;
+import android.os.SystemClock;
 import android.os.UserHandle;
 import android.provider.Settings;
+import android.util.IndentingPrintWriter;
 
 import androidx.test.filters.SmallTest;
 
@@ -59,6 +62,8 @@
 import org.mockito.MockitoAnnotations;
 import org.mockito.junit.MockitoJUnitRunner;
 
+import java.io.ByteArrayOutputStream;
+import java.io.PrintWriter;
 import java.lang.reflect.Array;
 import java.util.ArrayList;
 import java.util.List;
@@ -283,7 +288,7 @@
 
         final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
         final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane);
-        queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, 0);
+        queue.enqueueOrReplaceBroadcast(airplaneRecord, 0);
 
         queue.setProcessCached(false);
         final long notCachedRunnableAt = queue.getRunnableAt();
@@ -305,12 +310,12 @@
         // enqueue a bg-priority broadcast then a fg-priority one
         final Intent timezone = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
         final BroadcastRecord timezoneRecord = makeBroadcastRecord(timezone);
-        queue.enqueueOrReplaceBroadcast(timezoneRecord, 0, 0);
+        queue.enqueueOrReplaceBroadcast(timezoneRecord, 0);
 
         final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
         airplane.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
         final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane);
-        queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, 0);
+        queue.enqueueOrReplaceBroadcast(airplaneRecord, 0);
 
         // verify that:
         // (a) the queue is immediately runnable by existence of a fg-priority broadcast
@@ -339,9 +344,9 @@
 
         final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
         final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane, null,
-                List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN),
-                        makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN)), true);
-        queue.enqueueOrReplaceBroadcast(airplaneRecord, 1, 1);
+                List.of(withPriority(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), 10),
+                        withPriority(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN), 0)), true);
+        queue.enqueueOrReplaceBroadcast(airplaneRecord, 1);
 
         assertFalse(queue.isRunnable());
         assertEquals(BroadcastProcessQueue.REASON_BLOCKED, queue.getRunnableAtReason());
@@ -363,7 +368,7 @@
 
         final Intent airplane = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED);
         final BroadcastRecord airplaneRecord = makeBroadcastRecord(airplane);
-        queue.enqueueOrReplaceBroadcast(airplaneRecord, 0, 0);
+        queue.enqueueOrReplaceBroadcast(airplaneRecord, 0);
 
         mConstants.MAX_PENDING_BROADCASTS = 128;
         queue.invalidateRunnableAt();
@@ -377,6 +382,55 @@
     }
 
     /**
+     * Confirm that we always prefer running pending items marked as "urgent",
+     * then "normal", then "offload", dispatching by the relative ordering
+     * within each of those clustering groups.
+     */
+    @Test
+    public void testMakeActiveNextPending() {
+        BroadcastProcessQueue queue = new BroadcastProcessQueue(mConstants,
+                PACKAGE_GREEN, getUidForPackage(PACKAGE_GREEN));
+
+        queue.enqueueOrReplaceBroadcast(
+                makeBroadcastRecord(new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED)
+                        .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0);
+        queue.enqueueOrReplaceBroadcast(
+                makeBroadcastRecord(new Intent(Intent.ACTION_TIMEZONE_CHANGED)), 0);
+        queue.enqueueOrReplaceBroadcast(
+                makeBroadcastRecord(new Intent(Intent.ACTION_LOCKED_BOOT_COMPLETED)
+                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0);
+        queue.enqueueOrReplaceBroadcast(
+                makeBroadcastRecord(new Intent(Intent.ACTION_ALARM_CHANGED)
+                        .addFlags(Intent.FLAG_RECEIVER_OFFLOAD)), 0);
+        queue.enqueueOrReplaceBroadcast(
+                makeBroadcastRecord(new Intent(Intent.ACTION_TIME_TICK)), 0);
+        queue.enqueueOrReplaceBroadcast(
+                makeBroadcastRecord(new Intent(Intent.ACTION_LOCALE_CHANGED)
+                        .addFlags(Intent.FLAG_RECEIVER_FOREGROUND)), 0);
+
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_LOCKED_BOOT_COMPLETED, queue.getActive().intent.getAction());
+
+        // To maximize test coverage, dump current state; we're not worried
+        // about the actual output, just that we don't crash
+        queue.getActive().setDeliveryState(0, BroadcastRecord.DELIVERY_SCHEDULED);
+        queue.dumpLocked(SystemClock.uptimeMillis(),
+                new IndentingPrintWriter(new PrintWriter(new ByteArrayOutputStream())));
+
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_LOCALE_CHANGED, queue.getActive().intent.getAction());
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_TIMEZONE_CHANGED, queue.getActive().intent.getAction());
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_TIME_TICK, queue.getActive().intent.getAction());
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_AIRPLANE_MODE_CHANGED, queue.getActive().intent.getAction());
+        queue.makeActiveNextPending();
+        assertEquals(Intent.ACTION_ALARM_CHANGED, queue.getActive().intent.getAction());
+        assertTrue(queue.isEmpty());
+    }
+
+    /**
      * Verify that sending a broadcast that removes any matching pending
      * broadcasts is applied as expected.
      */
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index e1a4c1d..fd605f7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -280,13 +280,13 @@
         constants.TIMEOUT = 100;
         constants.ALLOW_BG_ACTIVITY_START_TIMEOUT = 0;
         final BroadcastSkipPolicy emptySkipPolicy = new BroadcastSkipPolicy(mAms) {
-            public boolean shouldSkip(BroadcastRecord r, ResolveInfo info) {
+            public boolean shouldSkip(BroadcastRecord r, Object o) {
                 // Ignored
                 return false;
             }
-            public boolean shouldSkip(BroadcastRecord r, BroadcastFilter filter) {
+            public String shouldSkipMessage(BroadcastRecord r, Object o) {
                 // Ignored
-                return false;
+                return null;
             }
         };
         final BroadcastHistory emptyHistory = new BroadcastHistory(constants) {
@@ -503,6 +503,11 @@
         return ai;
     }
 
+    static ResolveInfo withPriority(ResolveInfo info, int priority) {
+        info.priority = priority;
+        return info;
+    }
+
     static ResolveInfo makeManifestReceiver(String packageName, String name) {
         return makeManifestReceiver(packageName, name, UserHandle.USER_SYSTEM);
     }
@@ -1634,4 +1639,22 @@
         waitForIdle();
         verify(mAms, never()).enqueueOomAdjTargetLocked(any());
     }
+
+    /**
+     * Verify that expected events are triggered when a broadcast is finished.
+     */
+    @Test
+    public void testNotifyFinished() throws Exception {
+        final ProcessRecord callerApp = makeActiveProcessRecord(PACKAGE_RED);
+
+        final Intent intent = new Intent(Intent.ACTION_TIMEZONE_CHANGED);
+        final BroadcastRecord record = makeBroadcastRecord(intent, callerApp,
+                List.of(makeManifestReceiver(PACKAGE_GREEN, CLASS_GREEN)));
+        enqueueBroadcast(record);
+
+        waitForIdle();
+        verify(mAms).notifyBroadcastFinishedLocked(eq(record));
+        verify(mAms).addBroadcastStatLocked(eq(Intent.ACTION_TIMEZONE_CHANGED), eq(PACKAGE_RED),
+                eq(1), eq(0), anyLong());
+    }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
index 11573c5..05ed0e2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastRecordTest.java
@@ -24,9 +24,10 @@
 import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_ALL;
 import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_BACKGROUND_RESTRICTED_ONLY;
 import static com.android.server.am.BroadcastConstants.DEFER_BOOT_COMPLETED_BROADCAST_NONE;
-import static com.android.server.am.BroadcastRecord.isPrioritized;
+import static com.android.server.am.BroadcastRecord.calculateBlockedUntilTerminalCount;
 import static com.android.server.am.BroadcastRecord.isReceiverEquals;
 
+import static org.junit.Assert.assertArrayEquals;
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNull;
@@ -99,6 +100,16 @@
         assertFalse(isPrioritized(List.of(createResolveInfo(PACKAGE1, getAppId(1), 0))));
         assertFalse(isPrioritized(List.of(createResolveInfo(PACKAGE1, getAppId(1), -10))));
         assertFalse(isPrioritized(List.of(createResolveInfo(PACKAGE1, getAppId(1), 10))));
+
+        assertArrayEquals(new int[] {-1},
+                calculateBlockedUntilTerminalCount(List.of(
+                        createResolveInfo(PACKAGE1, getAppId(1), 0)), false));
+        assertArrayEquals(new int[] {-1},
+                calculateBlockedUntilTerminalCount(List.of(
+                        createResolveInfo(PACKAGE1, getAppId(1), -10)), false));
+        assertArrayEquals(new int[] {-1},
+                calculateBlockedUntilTerminalCount(List.of(
+                        createResolveInfo(PACKAGE1, getAppId(1), 10)), false));
     }
 
     @Test
@@ -111,6 +122,17 @@
                 createResolveInfo(PACKAGE1, getAppId(1), 10),
                 createResolveInfo(PACKAGE2, getAppId(2), 10),
                 createResolveInfo(PACKAGE3, getAppId(3), 10))));
+
+        assertArrayEquals(new int[] {-1,-1,-1},
+                calculateBlockedUntilTerminalCount(List.of(
+                        createResolveInfo(PACKAGE1, getAppId(1), 0),
+                        createResolveInfo(PACKAGE2, getAppId(2), 0),
+                        createResolveInfo(PACKAGE3, getAppId(3), 0)), false));
+        assertArrayEquals(new int[] {-1,-1,-1},
+                calculateBlockedUntilTerminalCount(List.of(
+                        createResolveInfo(PACKAGE1, getAppId(1), 10),
+                        createResolveInfo(PACKAGE2, getAppId(2), 10),
+                        createResolveInfo(PACKAGE3, getAppId(3), 10)), false));
     }
 
     @Test
@@ -123,6 +145,19 @@
                 createResolveInfo(PACKAGE1, getAppId(1), 0),
                 createResolveInfo(PACKAGE2, getAppId(2), 0),
                 createResolveInfo(PACKAGE3, getAppId(3), 10))));
+
+        assertArrayEquals(new int[] {0,1,2},
+                calculateBlockedUntilTerminalCount(List.of(
+                        createResolveInfo(PACKAGE1, getAppId(1), -10),
+                        createResolveInfo(PACKAGE2, getAppId(2), 0),
+                        createResolveInfo(PACKAGE3, getAppId(3), 10)), false));
+        assertArrayEquals(new int[] {0,0,2,3,3},
+                calculateBlockedUntilTerminalCount(List.of(
+                        createResolveInfo(PACKAGE1, getAppId(1), 0),
+                        createResolveInfo(PACKAGE2, getAppId(2), 0),
+                        createResolveInfo(PACKAGE3, getAppId(3), 10),
+                        createResolveInfo(PACKAGE3, getAppId(3), 20),
+                        createResolveInfo(PACKAGE3, getAppId(3), 20)), false));
     }
 
     @Test
@@ -543,4 +578,9 @@
     private static int getAppId(int i) {
         return Process.FIRST_APPLICATION_UID + i;
     }
+
+    private static boolean isPrioritized(List<Object> receivers) {
+        return BroadcastRecord.isPrioritized(
+                calculateBlockedUntilTerminalCount(receivers, false), false);
+    }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
index f46877e..2547347 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobConcurrencyManagerTest.java
@@ -16,10 +16,19 @@
 
 package com.android.server.job;
 
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.server.job.JobConcurrencyManager.KEY_PKG_CONCURRENCY_LIMIT_EJ;
 import static com.android.server.job.JobConcurrencyManager.KEY_PKG_CONCURRENCY_LIMIT_REGULAR;
+import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BG;
+import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BGUSER;
+import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BGUSER_IMPORTANT;
+import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_EJ;
+import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_FGS;
+import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_NONE;
+import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_TOP;
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
@@ -47,15 +56,6 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BG;
-import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BGUSER;
-import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_BGUSER_IMPORTANT;
-import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_EJ;
-import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_FGS;
-import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_NONE;
-import static com.android.server.job.JobConcurrencyManager.WORK_TYPE_TOP;
-
 import com.android.internal.R;
 import com.android.internal.app.IBatteryStats;
 import com.android.server.LocalServices;
@@ -73,6 +73,7 @@
 import org.mockito.Mock;
 import org.mockito.MockitoSession;
 import org.mockito.quality.Strictness;
+import org.mockito.stubbing.Answer;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -124,6 +125,7 @@
         mMockingSession = mockitoSession()
                 .initMocks(this)
                 .mockStatic(AppGlobals.class)
+                .spyStatic(DeviceConfig.class)
                 .strictness(Strictness.LENIENT)
                 .startMocking();
         final JobSchedulerService jobSchedulerService = mock(JobSchedulerService.class);
@@ -134,6 +136,8 @@
         when(mContext.getResources()).thenReturn(mResources);
         doReturn(mContext).when(jobSchedulerService).getTestableContext();
         mConfigBuilder = new DeviceConfig.Properties.Builder(DeviceConfig.NAMESPACE_JOB_SCHEDULER);
+        doAnswer((Answer<DeviceConfig.Properties>) invocationOnMock -> mConfigBuilder.build())
+                .when(() -> DeviceConfig.getProperties(eq(DeviceConfig.NAMESPACE_JOB_SCHEDULER)));
         mPendingJobQueue = new PendingJobQueue();
         doReturn(mPendingJobQueue).when(jobSchedulerService).getPendingJobQueue();
         doReturn(mIPackageManager).when(AppGlobals::getPackageManager);
@@ -595,7 +599,6 @@
     }
 
     private void updateDeviceConfig() throws Exception {
-        DeviceConfig.setProperties(mConfigBuilder.build());
         mJobConcurrencyManager.updateConfigLocked();
     }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
index 1753fc7..fc737d0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/JobSchedulerServiceTest.java
@@ -41,6 +41,7 @@
 import android.app.IActivityManager;
 import android.app.UiModeManager;
 import android.app.job.JobInfo;
+import android.app.job.JobParameters;
 import android.app.job.JobScheduler;
 import android.app.usage.UsageStatsManagerInternal;
 import android.content.ComponentName;
@@ -235,6 +236,59 @@
                 mService.getMinJobExecutionGuaranteeMs(jobDef));
     }
 
+
+    /**
+     * Confirm that {@link JobSchedulerService#getRescheduleJobForFailureLocked(JobStatus, int)}
+     * returns a job with the correct delay and deadline constraints.
+     */
+    @Test
+    public void testGetRescheduleJobForFailure() {
+        final long nowElapsed = sElapsedRealtimeClock.millis();
+        final long initialBackoffMs = MINUTE_IN_MILLIS;
+        mService.mConstants.SYSTEM_STOP_TO_FAILURE_RATIO = 3;
+
+        JobStatus originalJob = createJobStatus("testGetRescheduleJobForFailure",
+                createJobInfo()
+                        .setBackoffCriteria(initialBackoffMs, JobInfo.BACKOFF_POLICY_LINEAR));
+        assertEquals(JobStatus.NO_EARLIEST_RUNTIME, originalJob.getEarliestRunTime());
+        assertEquals(JobStatus.NO_LATEST_RUNTIME, originalJob.getLatestRunTimeElapsed());
+
+        // failure = 0, systemStop = 1
+        JobStatus rescheduledJob = mService.getRescheduleJobForFailureLocked(originalJob,
+                JobParameters.INTERNAL_STOP_REASON_DEVICE_THERMAL);
+        assertEquals(nowElapsed + initialBackoffMs, rescheduledJob.getEarliestRunTime());
+        assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
+
+        // failure = 0, systemStop = 2
+        rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
+                JobParameters.INTERNAL_STOP_REASON_PREEMPT);
+        // failure = 0, systemStop = 3
+        rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
+                JobParameters.INTERNAL_STOP_REASON_CONSTRAINTS_NOT_SATISFIED);
+        assertEquals(nowElapsed + initialBackoffMs, rescheduledJob.getEarliestRunTime());
+        assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
+
+        // failure = 0, systemStop = 2 * SYSTEM_STOP_TO_FAILURE_RATIO
+        for (int i = 0; i < mService.mConstants.SYSTEM_STOP_TO_FAILURE_RATIO; ++i) {
+            rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
+                    JobParameters.INTERNAL_STOP_REASON_RTC_UPDATED);
+        }
+        assertEquals(nowElapsed + 2 * initialBackoffMs, rescheduledJob.getEarliestRunTime());
+        assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
+
+        // failure = 1, systemStop = 2 * SYSTEM_STOP_TO_FAILURE_RATIO
+        rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
+                JobParameters.INTERNAL_STOP_REASON_TIMEOUT);
+        assertEquals(nowElapsed + 3 * initialBackoffMs, rescheduledJob.getEarliestRunTime());
+        assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
+
+        // failure = 2, systemStop = 2 * SYSTEM_STOP_TO_FAILURE_RATIO
+        rescheduledJob = mService.getRescheduleJobForFailureLocked(rescheduledJob,
+                JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH);
+        assertEquals(nowElapsed + 4 * initialBackoffMs, rescheduledJob.getEarliestRunTime());
+        assertEquals(JobStatus.NO_LATEST_RUNTIME, rescheduledJob.getLatestRunTimeElapsed());
+    }
+
     /**
      * Confirm that {@link JobSchedulerService#getRescheduleJobForPeriodic(JobStatus)} returns a job
      * with the correct delay and deadline constraints if the periodic job is scheduled with the
@@ -544,14 +598,16 @@
         final long nextWindowEndTime = now + 2 * HOUR_IN_MILLIS;
         JobStatus job = createJobStatus("testGetRescheduleJobForPeriodic_insideWindow_failedJob",
                 createJobInfo().setPeriodic(HOUR_IN_MILLIS));
-        JobStatus failedJob = mService.getRescheduleJobForFailureLocked(job);
+        JobStatus failedJob = mService.getRescheduleJobForFailureLocked(job,
+                JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH);
 
         JobStatus rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob);
         assertEquals(nextWindowStartTime, rescheduledJob.getEarliestRunTime());
         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
 
         advanceElapsedClock(5 * MINUTE_IN_MILLIS); // now + 5 minutes
-        failedJob = mService.getRescheduleJobForFailureLocked(job);
+        failedJob = mService.getRescheduleJobForFailureLocked(job,
+                JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH);
         advanceElapsedClock(5 * MINUTE_IN_MILLIS); // now + 10 minutes
 
         rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob);
@@ -559,7 +615,8 @@
         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
 
         advanceElapsedClock(35 * MINUTE_IN_MILLIS); // now + 45 minutes
-        failedJob = mService.getRescheduleJobForFailureLocked(job);
+        failedJob = mService.getRescheduleJobForFailureLocked(job,
+                JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH);
         advanceElapsedClock(10 * MINUTE_IN_MILLIS); // now + 55 minutes
 
         rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob);
@@ -569,7 +626,8 @@
         assertEquals(nextWindowEndTime, rescheduledJob.getLatestRunTimeElapsed());
 
         advanceElapsedClock(2 * MINUTE_IN_MILLIS); // now + 57 minutes
-        failedJob = mService.getRescheduleJobForFailureLocked(job);
+        failedJob = mService.getRescheduleJobForFailureLocked(job,
+                JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH);
         advanceElapsedClock(2 * MINUTE_IN_MILLIS); // now + 59 minutes
 
         rescheduledJob = mService.getRescheduleJobForPeriodic(failedJob);
@@ -665,7 +723,8 @@
     public void testGetRescheduleJobForPeriodic_outsideWindow_failedJob() {
         JobStatus job = createJobStatus("testGetRescheduleJobForPeriodic_outsideWindow_failedJob",
                 createJobInfo().setPeriodic(HOUR_IN_MILLIS));
-        JobStatus failedJob = mService.getRescheduleJobForFailureLocked(job);
+        JobStatus failedJob = mService.getRescheduleJobForFailureLocked(job,
+                JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH);
         long now = sElapsedRealtimeClock.millis();
         long nextWindowStartTime = now + HOUR_IN_MILLIS;
         long nextWindowEndTime = now + 2 * HOUR_IN_MILLIS;
@@ -701,7 +760,8 @@
         JobStatus job = createJobStatus(
                 "testGetRescheduleJobForPeriodic_outsideWindow_flex_failedJob",
                 createJobInfo().setPeriodic(HOUR_IN_MILLIS, 30 * MINUTE_IN_MILLIS));
-        JobStatus failedJob = mService.getRescheduleJobForFailureLocked(job);
+        JobStatus failedJob = mService.getRescheduleJobForFailureLocked(job,
+                JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH);
         // First window starts 30 minutes from now.
         advanceElapsedClock(30 * MINUTE_IN_MILLIS);
         long now = sElapsedRealtimeClock.millis();
@@ -742,7 +802,8 @@
         JobStatus job = createJobStatus(
                 "testGetRescheduleJobForPeriodic_outsideWindow_flex_failedJob_longPeriod",
                 createJobInfo().setPeriodic(7 * DAY_IN_MILLIS, 9 * HOUR_IN_MILLIS));
-        JobStatus failedJob = mService.getRescheduleJobForFailureLocked(job);
+        JobStatus failedJob = mService.getRescheduleJobForFailureLocked(job,
+                JobParameters.INTERNAL_STOP_REASON_SUCCESSFUL_FINISH);
         // First window starts 6.625 days from now.
         advanceElapsedClock(6 * DAY_IN_MILLIS + 15 * HOUR_IN_MILLIS);
         long now = sElapsedRealtimeClock.millis();
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java
index 59cb43f..7c435be 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BatteryControllerTest.java
@@ -432,10 +432,10 @@
         assertFalse(topStartedJobs.contains(unrelatedJob));
 
         // Job cleanup
-        mBatteryController.maybeStopTrackingJobLocked(batteryJob, null, false);
-        mBatteryController.maybeStopTrackingJobLocked(chargingJob, null, false);
-        mBatteryController.maybeStopTrackingJobLocked(bothPowerJob, null, false);
-        mBatteryController.maybeStopTrackingJobLocked(unrelatedJob, null, false);
+        mBatteryController.maybeStopTrackingJobLocked(batteryJob, null);
+        mBatteryController.maybeStopTrackingJobLocked(chargingJob, null);
+        mBatteryController.maybeStopTrackingJobLocked(bothPowerJob, null);
+        mBatteryController.maybeStopTrackingJobLocked(unrelatedJob, null);
         assertFalse(trackedJobs.contains(batteryJob));
         assertFalse(trackedJobs.contains(chargingJob));
         assertFalse(trackedJobs.contains(bothPowerJob));
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
index 674e500..3bee687 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/FlexibilityControllerTest.java
@@ -489,19 +489,22 @@
         JobInfo.Builder jb = createJob(0).setOverrideDeadline(1000L);
         JobStatus js = createJobStatus("time", jb);
         js = new JobStatus(
-                js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 2, FROZEN_TIME, FROZEN_TIME);
+                js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 2, /* numSystemStops */ 0,
+                FROZEN_TIME, FROZEN_TIME);
 
         assertEquals(mFcConfig.RESCHEDULED_JOB_DEADLINE_MS,
                 mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0));
 
         js = new JobStatus(
-                js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 3, FROZEN_TIME, FROZEN_TIME);
+                js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 2, /* numSystemStops */ 1,
+                FROZEN_TIME, FROZEN_TIME);
 
         assertEquals(2 * mFcConfig.RESCHEDULED_JOB_DEADLINE_MS,
                 mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0));
 
         js = new JobStatus(
-                js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 10, FROZEN_TIME, FROZEN_TIME);
+                js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 0, /* numSystemStops */ 10,
+                FROZEN_TIME, FROZEN_TIME);
         assertEquals(mFcConfig.MAX_RESCHEDULED_DEADLINE_MS,
                 mFlexibilityController.getLifeCycleEndElapsedLocked(js, 0));
     }
@@ -637,7 +640,12 @@
         JobInfo.Builder jb = createJob(0);
         JobStatus js = createJobStatus("time", jb);
         js = new JobStatus(
-                js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 1, FROZEN_TIME, FROZEN_TIME);
+                js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 1, /* numSystemStops */ 0,
+                FROZEN_TIME, FROZEN_TIME);
+        assertFalse(js.hasFlexibilityConstraint());
+        js = new JobStatus(
+                js, FROZEN_TIME, NO_LATEST_RUNTIME, /* numFailures */ 0, /* numSystemStops */ 1,
+                FROZEN_TIME, FROZEN_TIME);
         assertFalse(js.hasFlexibilityConstraint());
     }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
index 149ae0b..7f522b0 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/JobStatusTest.java
@@ -248,20 +248,32 @@
 
         // Less than 2 failures, priority shouldn't be affected.
         assertEquals(JobInfo.PRIORITY_MAX, job.getEffectivePriority());
-        int backoffAttempt = 1;
-        job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, backoffAttempt, 0, 0);
+        int numFailures = 1;
+        int numSystemStops = 0;
+        job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
+                numSystemStops, 0, 0);
         assertEquals(JobInfo.PRIORITY_MAX, job.getEffectivePriority());
 
         // 2+ failures, priority should be lowered as much as possible.
-        backoffAttempt = 2;
-        job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, backoffAttempt, 0, 0);
+        numFailures = 2;
+        job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
+                numSystemStops, 0, 0);
         assertEquals(JobInfo.PRIORITY_HIGH, job.getEffectivePriority());
-        backoffAttempt = 5;
-        job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, backoffAttempt, 0, 0);
+        numFailures = 5;
+        job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
+                numSystemStops, 0, 0);
         assertEquals(JobInfo.PRIORITY_HIGH, job.getEffectivePriority());
-        backoffAttempt = 8;
-        job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, backoffAttempt, 0, 0);
+        numFailures = 8;
+        job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
+                numSystemStops, 0, 0);
         assertEquals(JobInfo.PRIORITY_HIGH, job.getEffectivePriority());
+
+        // System stops shouldn't factor in the downgrade.
+        numSystemStops = 10;
+        numFailures = 0;
+        job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
+                numSystemStops, 0, 0);
+        assertEquals(JobInfo.PRIORITY_MAX, job.getEffectivePriority());
     }
 
     @Test
@@ -274,33 +286,48 @@
 
         // Less than 2 failures, priority shouldn't be affected.
         assertEquals(JobInfo.PRIORITY_HIGH, job.getEffectivePriority());
-        int backoffAttempt = 1;
-        job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, backoffAttempt, 0, 0);
+        int numFailures = 1;
+        int numSystemStops = 0;
+        job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
+                numSystemStops, 0, 0);
         assertEquals(JobInfo.PRIORITY_HIGH, job.getEffectivePriority());
 
         // Failures in [2,4), priority should be lowered slightly.
-        backoffAttempt = 2;
-        job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, backoffAttempt, 0, 0);
+        numFailures = 2;
+        job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
+                numSystemStops, 0, 0);
         assertEquals(JobInfo.PRIORITY_DEFAULT, job.getEffectivePriority());
-        backoffAttempt = 3;
-        job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, backoffAttempt, 0, 0);
+        numFailures = 3;
+        job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
+                numSystemStops, 0, 0);
         assertEquals(JobInfo.PRIORITY_DEFAULT, job.getEffectivePriority());
 
         // Failures in [4,6), priority should be lowered more.
-        backoffAttempt = 4;
-        job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, backoffAttempt, 0, 0);
+        numFailures = 4;
+        job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
+                numSystemStops, 0, 0);
         assertEquals(JobInfo.PRIORITY_LOW, job.getEffectivePriority());
-        backoffAttempt = 5;
-        job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, backoffAttempt, 0, 0);
+        numFailures = 5;
+        job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
+                numSystemStops, 0, 0);
         assertEquals(JobInfo.PRIORITY_LOW, job.getEffectivePriority());
 
         // 6+ failures, priority should be lowered as much as possible.
-        backoffAttempt = 6;
-        job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, backoffAttempt, 0, 0);
+        numFailures = 6;
+        job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
+                numSystemStops, 0, 0);
         assertEquals(JobInfo.PRIORITY_MIN, job.getEffectivePriority());
-        backoffAttempt = 12;
-        job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, backoffAttempt, 0, 0);
+        numFailures = 12;
+        job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
+                numSystemStops, 0, 0);
         assertEquals(JobInfo.PRIORITY_MIN, job.getEffectivePriority());
+
+        // System stops shouldn't factor in the downgrade.
+        numSystemStops = 10;
+        numFailures = 0;
+        job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
+                numSystemStops, 0, 0);
+        assertEquals(JobInfo.PRIORITY_HIGH, job.getEffectivePriority());
     }
 
     /**
@@ -317,23 +344,36 @@
 
         // Less than 6 failures, priority shouldn't be affected.
         assertEquals(JobInfo.PRIORITY_LOW, job.getEffectivePriority());
-        int backoffAttempt = 1;
-        job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, backoffAttempt, 0, 0);
+        int numFailures = 1;
+        int numSystemStops = 0;
+        job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
+                numSystemStops, 0, 0);
         assertEquals(JobInfo.PRIORITY_LOW, job.getEffectivePriority());
-        backoffAttempt = 4;
-        job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, backoffAttempt, 0, 0);
+        numFailures = 4;
+        job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
+                numSystemStops, 0, 0);
         assertEquals(JobInfo.PRIORITY_LOW, job.getEffectivePriority());
-        backoffAttempt = 5;
-        job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, backoffAttempt, 0, 0);
+        numFailures = 5;
+        job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
+                numSystemStops, 0, 0);
         assertEquals(JobInfo.PRIORITY_LOW, job.getEffectivePriority());
 
         // 6+ failures, priority should be lowered as much as possible.
-        backoffAttempt = 6;
-        job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, backoffAttempt, 0, 0);
+        numFailures = 6;
+        job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
+                numSystemStops, 0, 0);
         assertEquals(JobInfo.PRIORITY_MIN, job.getEffectivePriority());
-        backoffAttempt = 12;
-        job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, backoffAttempt, 0, 0);
+        numFailures = 12;
+        job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
+                numSystemStops, 0, 0);
         assertEquals(JobInfo.PRIORITY_MIN, job.getEffectivePriority());
+
+        // System stops shouldn't factor in the downgrade.
+        numSystemStops = 10;
+        numFailures = 0;
+        job = new JobStatus(job, NO_EARLIEST_RUNTIME, NO_LATEST_RUNTIME, numFailures,
+                numSystemStops, 0, 0);
+        assertEquals(JobInfo.PRIORITY_LOW, job.getEffectivePriority());
     }
 
     /**
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
index bb477b1..b949b3b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/PrefetchControllerTest.java
@@ -47,6 +47,7 @@
 import android.appwidget.AppWidgetManager;
 import android.content.ComponentName;
 import android.content.Context;
+import android.os.Handler;
 import android.os.Looper;
 import android.os.Process;
 import android.os.SystemClock;
@@ -276,13 +277,13 @@
         inOrder.verify(mAlarmManager, timeout(DEFAULT_WAIT_MS).times(1))
                 .setWindow(
                         anyInt(), eq(sElapsedRealtimeClock.millis() + 4 * HOUR_IN_MILLIS),
-                        anyLong(), eq(TAG_PREFETCH), any(), any());
+                        anyLong(), eq(TAG_PREFETCH), any(), any(Handler.class));
 
         setDeviceConfigLong(PcConstants.KEY_LAUNCH_TIME_THRESHOLD_MS, 3 * HOUR_IN_MILLIS);
         inOrder.verify(mAlarmManager, timeout(DEFAULT_WAIT_MS).times(1))
                 .setWindow(
                         anyInt(), eq(sElapsedRealtimeClock.millis() + 8 * HOUR_IN_MILLIS),
-                        anyLong(), eq(TAG_PREFETCH), any(), any());
+                        anyLong(), eq(TAG_PREFETCH), any(), any(Handler.class));
     }
 
     @Test
@@ -414,7 +415,7 @@
         verify(mAlarmManager, timeout(DEFAULT_WAIT_MS).times(1))
                 .setWindow(
                         anyInt(), eq(sElapsedRealtimeClock.millis() + 3 * HOUR_IN_MILLIS),
-                        anyLong(), eq(TAG_PREFETCH), any(), any());
+                        anyLong(), eq(TAG_PREFETCH), any(), any(Handler.class));
         assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
         assertFalse(jobStatus.isReady());
     }
@@ -464,7 +465,7 @@
         verify(mAlarmManager, timeout(DEFAULT_WAIT_MS).times(1))
                 .setWindow(
                         anyInt(), eq(sElapsedRealtimeClock.millis() + 3 * HOUR_IN_MILLIS),
-                        anyLong(), eq(TAG_PREFETCH), any(), any());
+                        anyLong(), eq(TAG_PREFETCH), any(), any(Handler.class));
         assertFalse(jobStatus.isConstraintSatisfied(JobStatus.CONSTRAINT_PREFETCH));
         assertFalse(jobStatus.isReady());
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
index 9407968..17822c6 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/QuotaControllerTest.java
@@ -1356,7 +1356,7 @@
         synchronized (mQuotaController.mLock) {
             assertEquals(JobSchedulerService.Constants.DEFAULT_RUNTIME_FREE_QUOTA_MAX_LIMIT_MS,
                     mQuotaController.getMaxJobExecutionTimeMsLocked((job)));
-            mQuotaController.maybeStopTrackingJobLocked(job, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(job, null);
         }
 
         setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
@@ -1441,7 +1441,7 @@
         synchronized (mQuotaController.mLock) {
             assertEquals(mQcConstants.EJ_LIMIT_ACTIVE_MS / 2,
                     mQuotaController.getMaxJobExecutionTimeMsLocked(job));
-            mQuotaController.maybeStopTrackingJobLocked(job, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(job, null);
         }
 
         setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
@@ -1474,7 +1474,7 @@
         synchronized (mQuotaController.mLock) {
             assertEquals(mQcConstants.EJ_LIMIT_ACTIVE_MS / 2,
                     mQuotaController.getMaxJobExecutionTimeMsLocked(job));
-            mQuotaController.maybeStopTrackingJobLocked(job, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(job, null);
         }
 
         setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
@@ -2017,7 +2017,7 @@
             setProcessState(ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND);
         }
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(jobStatus, null);
         }
 
         advanceElapsedClock(15 * SECOND_IN_MILLIS);
@@ -2032,7 +2032,7 @@
             setProcessState(ActivityManager.PROCESS_STATE_RECEIVER);
         }
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(jobStatus, null);
         }
 
         advanceElapsedClock(10 * MINUTE_IN_MILLIS + 30 * SECOND_IN_MILLIS);
@@ -2090,7 +2090,7 @@
             setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING, fgChangerUid);
         }
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(fgStateChanger, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(fgStateChanger, null);
         }
 
         advanceElapsedClock(15 * SECOND_IN_MILLIS);
@@ -2105,9 +2105,9 @@
             setProcessState(ActivityManager.PROCESS_STATE_TOP_SLEEPING, fgChangerUid);
         }
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(fgStateChanger, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(fgStateChanger, null);
 
-            mQuotaController.maybeStopTrackingJobLocked(unaffected, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(unaffected, null);
 
             assertTrue(mQuotaController.isWithinQuotaLocked(unaffected));
             assertTrue(unaffected.isReady());
@@ -2226,8 +2226,8 @@
 
         advanceElapsedClock(MINUTE_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(job1, null, false);
-            mQuotaController.maybeStopTrackingJobLocked(job2, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(job1, null);
+            mQuotaController.maybeStopTrackingJobLocked(job2, null);
             assertFalse(mQuotaController
                     .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
             assertEquals(7 * MINUTE_IN_MILLIS, stats.allowedTimePerPeriodMs);
@@ -2312,8 +2312,8 @@
 
         advanceElapsedClock(MINUTE_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(job1, null, false);
-            mQuotaController.maybeStopTrackingJobLocked(job2, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(job1, null);
+            mQuotaController.maybeStopTrackingJobLocked(job2, null);
             assertFalse(mQuotaController
                     .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
             assertEquals(12, stats.jobCountLimit);
@@ -2390,7 +2390,7 @@
         advanceElapsedClock(MINUTE_IN_MILLIS);
         mAppIdleStateChangeListener.triggerTemporaryQuotaBump(SOURCE_PACKAGE, SOURCE_USER_ID);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(job1, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(job1, null);
             assertTrue(mQuotaController
                     .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
             assertEquals(4, stats.sessionCountLimit);
@@ -2404,7 +2404,7 @@
 
         advanceElapsedClock(MINUTE_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(job2, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(job2, null);
             assertFalse(mQuotaController
                     .isWithinQuotaLocked(SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX));
             assertEquals(4, stats.sessionCountLimit);
@@ -2708,7 +2708,7 @@
                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
-                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
 
         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
         // Test with timing sessions out of window but still under max execution limit.
@@ -2725,7 +2725,7 @@
                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
-                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
 
         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
                 createTimingSession(now - 2 * HOUR_IN_MILLIS, 55 * MINUTE_IN_MILLIS, 1), false);
@@ -2734,7 +2734,7 @@
                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
-                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
 
         synchronized (mQuotaController.mLock) {
             mQuotaController.prepareForExecutionLocked(jobStatus);
@@ -2749,7 +2749,8 @@
                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
         verify(mAlarmManager, timeout(1000).times(1)).setWindow(
-                anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(),
+                any(Handler.class));
     }
 
     @Test
@@ -2771,7 +2772,7 @@
                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
-                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
 
         // Test with timing sessions out of window.
         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
@@ -2782,7 +2783,7 @@
                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
-                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
 
         // Test with timing sessions in window but still in quota.
         final long end = now - (2 * HOUR_IN_MILLIS - 5 * MINUTE_IN_MILLIS);
@@ -2796,7 +2797,7 @@
                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
-                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
 
         // Add some more sessions, but still in quota.
         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
@@ -2808,7 +2809,7 @@
                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
-                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
 
         // Test when out of quota.
         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
@@ -2818,7 +2819,8 @@
                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
         verify(mAlarmManager, timeout(1000).times(1)).setWindow(
-                anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(),
+                any(Handler.class));
 
         // Alarm already scheduled, so make sure it's not scheduled again.
         synchronized (mQuotaController.mLock) {
@@ -2826,7 +2828,8 @@
                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
         verify(mAlarmManager, timeout(1000).times(1)).setWindow(
-                anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(),
+                any(Handler.class));
     }
 
     @Test
@@ -2850,7 +2853,7 @@
                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
-                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
 
         // Test with timing sessions out of window.
         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
@@ -2861,7 +2864,7 @@
                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
-                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
 
         // Test with timing sessions in window but still in quota.
         final long start = now - (6 * HOUR_IN_MILLIS);
@@ -2873,7 +2876,7 @@
                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
-                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
 
         // Add some more sessions, but still in quota.
         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
@@ -2885,7 +2888,7 @@
                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
-                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
 
         // Test when out of quota.
         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
@@ -2895,7 +2898,8 @@
                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
         verify(mAlarmManager, timeout(1000).times(1)).setWindow(
-                anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(),
+                any(Handler.class));
 
         // Alarm already scheduled, so make sure it's not scheduled again.
         synchronized (mQuotaController.mLock) {
@@ -2903,7 +2907,8 @@
                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
         verify(mAlarmManager, timeout(1000).times(1)).setWindow(
-                anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(),
+                any(Handler.class));
     }
 
     /**
@@ -2932,7 +2937,7 @@
                     SOURCE_USER_ID, SOURCE_PACKAGE, effectiveStandbyBucket);
         }
         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
-                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
 
         // Test with timing sessions out of window.
         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
@@ -2943,7 +2948,7 @@
                     SOURCE_USER_ID, SOURCE_PACKAGE, effectiveStandbyBucket);
         }
         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
-                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
 
         // Test with timing sessions in window but still in quota.
         final long start = now - (6 * HOUR_IN_MILLIS);
@@ -2955,7 +2960,7 @@
                     SOURCE_USER_ID, SOURCE_PACKAGE, effectiveStandbyBucket);
         }
         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
-                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
 
         // Add some more sessions, but still in quota.
         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
@@ -2967,7 +2972,7 @@
                     SOURCE_USER_ID, SOURCE_PACKAGE, effectiveStandbyBucket);
         }
         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
-                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
 
         // Test when out of quota.
         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
@@ -2977,7 +2982,8 @@
                     SOURCE_USER_ID, SOURCE_PACKAGE, effectiveStandbyBucket);
         }
         verify(mAlarmManager, timeout(1000).times(1)).setWindow(
-                anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(),
+                any(Handler.class));
 
         // Alarm already scheduled, so make sure it's not scheduled again.
         synchronized (mQuotaController.mLock) {
@@ -2985,7 +2991,8 @@
                     SOURCE_USER_ID, SOURCE_PACKAGE, effectiveStandbyBucket);
         }
         verify(mAlarmManager, timeout(1000).times(1)).setWindow(
-                anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(),
+                any(Handler.class));
     }
 
     @Test
@@ -3013,7 +3020,7 @@
                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
-                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
 
         // Test with timing sessions out of window.
         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
@@ -3023,7 +3030,7 @@
             mQuotaController.maybeScheduleStartAlarmLocked(0, "com.android.test", standbyBucket);
         }
         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
-                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
 
         // Test with timing sessions in window but still in quota.
         final long start = now - (6 * HOUR_IN_MILLIS);
@@ -3039,7 +3046,7 @@
                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
-                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
 
         // Add some more sessions, but still in quota.
         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
@@ -3051,7 +3058,7 @@
                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
-                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
 
         // Test when out of quota.
         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
@@ -3061,7 +3068,8 @@
                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
         verify(mAlarmManager, timeout(1000).times(1)).setWindow(
-                anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(),
+                any(Handler.class));
 
         // Alarm already scheduled, so make sure it's not scheduled again.
         synchronized (mQuotaController.mLock) {
@@ -3069,7 +3077,8 @@
                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
         verify(mAlarmManager, timeout(1000).times(1)).setWindow(
-                anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(),
+                any(Handler.class));
     }
 
     /** Tests that the start alarm is properly rescheduled if the app's bucket is changed. */
@@ -3108,7 +3117,8 @@
                     SOURCE_USER_ID, SOURCE_PACKAGE, ACTIVE_INDEX);
         }
         inOrder.verify(mAlarmManager, timeout(1000).times(0))
-                .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(),
+                        any(Handler.class));
         inOrder.verify(mAlarmManager, timeout(1000).times(0))
                 .cancel(any(AlarmManager.OnAlarmListener.class));
 
@@ -3123,7 +3133,7 @@
         }
         inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
                 anyInt(), eq(expectedWorkingAlarmTime), anyLong(),
-                eq(TAG_QUOTA_CHECK), any(), any());
+                eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
 
         final long expectedFrequentAlarmTime =
                 outOfQuotaTime + (8 * HOUR_IN_MILLIS)
@@ -3135,7 +3145,7 @@
         }
         inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
                 anyInt(), eq(expectedFrequentAlarmTime), anyLong(),
-                eq(TAG_QUOTA_CHECK), any(), any());
+                eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
 
         final long expectedRareAlarmTime =
                 outOfQuotaTime + (24 * HOUR_IN_MILLIS)
@@ -3146,7 +3156,8 @@
                     SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX);
         }
         inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
-                anyInt(), eq(expectedRareAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                anyInt(), eq(expectedRareAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(),
+                any(Handler.class));
 
         // And back up again.
         setStandbyBucket(FREQUENT_INDEX, jobStatus);
@@ -3156,7 +3167,7 @@
         }
         inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
                 anyInt(), eq(expectedFrequentAlarmTime), anyLong(),
-                eq(TAG_QUOTA_CHECK), any(), any());
+                eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
 
         setStandbyBucket(WORKING_INDEX, jobStatus);
         synchronized (mQuotaController.mLock) {
@@ -3165,7 +3176,7 @@
         }
         inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
                 anyInt(), eq(expectedWorkingAlarmTime), anyLong(),
-                eq(TAG_QUOTA_CHECK), any(), any());
+                eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
 
         setStandbyBucket(ACTIVE_INDEX, jobStatus);
         synchronized (mQuotaController.mLock) {
@@ -3173,7 +3184,8 @@
                     SOURCE_USER_ID, SOURCE_PACKAGE, ACTIVE_INDEX);
         }
         inOrder.verify(mAlarmManager, timeout(1000).times(0))
-                .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(),
+                        any(Handler.class));
         inOrder.verify(mAlarmManager, timeout(1000).times(1))
                 .cancel(any(AlarmManager.OnAlarmListener.class));
     }
@@ -3210,7 +3222,7 @@
                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
-                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
 
         // Valid time in the future, so the count should be used.
         stats.jobRateLimitExpirationTimeElapsed = now + 5 * MINUTE_IN_MILLIS / 2;
@@ -3221,7 +3233,7 @@
         }
         verify(mAlarmManager, timeout(1000).times(1)).setWindow(
                 anyInt(), eq(expectedWorkingAlarmTime), anyLong(),
-                eq(TAG_QUOTA_CHECK), any(), any());
+                eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
     }
 
     /**
@@ -3318,7 +3330,8 @@
                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
         verify(mAlarmManager, timeout(1000).times(1)).setWindow(
-                anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(),
+                any(Handler.class));
     }
 
     private void runTestMaybeScheduleStartAlarmLocked_SmallRollingQuota_MaxTimeCheck() {
@@ -3355,7 +3368,8 @@
                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
         verify(mAlarmManager, timeout(1000).times(1)).setWindow(
-                anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(),
+                any(Handler.class));
     }
 
     @Test
@@ -3711,7 +3725,7 @@
         }
         advanceElapsedClock(5 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(jobStatus, null);
         }
         assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
     }
@@ -3737,7 +3751,7 @@
         }
         advanceElapsedClock(5 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(jobStatus, null);
         }
         expected.add(createTimingSession(start, 5 * SECOND_IN_MILLIS, 1));
         assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
@@ -3766,7 +3780,7 @@
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(jobStatus, null);
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
@@ -3774,11 +3788,11 @@
         }
         advanceElapsedClock(20 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null);
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null);
         }
         expected.add(createTimingSession(start, MINUTE_IN_MILLIS, 3));
         assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
@@ -3819,7 +3833,7 @@
         long start = JobSchedulerService.sElapsedRealtimeClock.millis();
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobStatus, jobStatus, true);
+            mQuotaController.maybeStopTrackingJobLocked(jobStatus, jobStatus);
         }
         expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
         assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
@@ -3850,18 +3864,18 @@
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null);
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null);
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         setDischarging();
         start = JobSchedulerService.sElapsedRealtimeClock.millis();
         advanceElapsedClock(20 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(jobStatus, null);
         }
         expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 1));
         assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
@@ -3879,7 +3893,7 @@
         setCharging();
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null);
         }
         assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
     }
@@ -3905,7 +3919,7 @@
         }
         advanceElapsedClock(5 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(jobStatus, null);
         }
         expected.add(createTimingSession(start, 5 * SECOND_IN_MILLIS, 1));
         assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
@@ -3934,7 +3948,7 @@
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(jobStatus, null);
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
@@ -3942,11 +3956,11 @@
         }
         advanceElapsedClock(20 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null);
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null);
         }
         expected.add(createTimingSession(start, MINUTE_IN_MILLIS, 3));
         assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
@@ -3973,7 +3987,7 @@
         setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
         advanceElapsedClock(5 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(jobStatus, null);
         }
         assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
     }
@@ -4005,7 +4019,7 @@
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true);
+            mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1);
         }
         expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
         assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
@@ -4030,11 +4044,11 @@
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobFg3, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(jobFg3, null);
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(jobBg2, null);
         }
         assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
 
@@ -4063,7 +4077,7 @@
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true);
+            mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1);
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS); // UID "inactive" now
         start = JobSchedulerService.sElapsedRealtimeClock.millis();
@@ -4073,11 +4087,11 @@
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobFg3, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(jobFg3, null);
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(jobBg2, null);
         }
         expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 2));
         assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
@@ -4116,11 +4130,11 @@
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobFg1, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(jobFg1, null);
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobFg2, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(jobFg2, null);
         }
         assertNull(mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
 
@@ -4155,11 +4169,11 @@
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobBg1, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(jobBg1, null);
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(jobBg2, null);
         }
 
         assertEquals(2, stats.jobCountInRateLimitingWindow);
@@ -4193,7 +4207,7 @@
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true);
+            mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1);
         }
         expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
         assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
@@ -4218,11 +4232,11 @@
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobTop, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(jobTop, null);
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(jobBg2, null);
         }
         assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
 
@@ -4253,7 +4267,7 @@
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true);
+            mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1);
         }
         advanceElapsedClock(5 * SECOND_IN_MILLIS);
         setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
@@ -4270,12 +4284,12 @@
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobTop, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(jobTop, null);
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
-            mQuotaController.maybeStopTrackingJobLocked(jobFg1, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(jobBg2, null);
+            mQuotaController.maybeStopTrackingJobLocked(jobFg1, null);
         }
         expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 2));
         assertEquals(expected, mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
@@ -4312,7 +4326,7 @@
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(job1, job1, true);
+            mQuotaController.maybeStopTrackingJobLocked(job1, job1);
         }
         expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
         assertEquals(expected,
@@ -4329,7 +4343,7 @@
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(job2, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(job2, null);
         }
         expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
         assertEquals(expected,
@@ -4348,7 +4362,7 @@
         long elapsedGracePeriodMs = 2 * SECOND_IN_MILLIS;
         advanceElapsedClock(elapsedGracePeriodMs);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(job3, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(job3, null);
         }
         expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS + elapsedGracePeriodMs, 1));
         assertEquals(expected,
@@ -4373,7 +4387,7 @@
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         expected.add(createTimingSession(start, remainingGracePeriod + 10 * SECOND_IN_MILLIS, 1));
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(job4, job4, true);
+            mQuotaController.maybeStopTrackingJobLocked(job4, job4);
         }
         assertEquals(expected,
                 mQuotaController.getTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
@@ -4387,7 +4401,7 @@
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(job5, job5, true);
+            mQuotaController.maybeStopTrackingJobLocked(job5, job5);
         }
         expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
         assertEquals(expected,
@@ -4611,7 +4625,7 @@
                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
-                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
 
         // Ran jobs up to the job limit. All of them should be allowed to run.
         for (int i = 0; i < mQcConstants.MAX_JOB_COUNT_PER_RATE_LIMITING_WINDOW; ++i) {
@@ -4624,13 +4638,13 @@
             }
             advanceElapsedClock(SECOND_IN_MILLIS);
             synchronized (mQuotaController.mLock) {
-                mQuotaController.maybeStopTrackingJobLocked(job, null, false);
+                mQuotaController.maybeStopTrackingJobLocked(job, null);
             }
             advanceElapsedClock(SECOND_IN_MILLIS);
         }
         // Start alarm shouldn't have been scheduled since the app was in quota up until this point.
         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
-                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
 
         // The app is now out of job count quota
         JobStatus throttledJob = createJobStatus(
@@ -4649,7 +4663,7 @@
         final long expectedWorkingAlarmTime = stats.jobRateLimitExpirationTimeElapsed;
         verify(mAlarmManager, timeout(1000).times(1)).setWindow(
                 anyInt(), eq(expectedWorkingAlarmTime), anyLong(),
-                eq(TAG_QUOTA_CHECK), any(), any());
+                eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
     }
 
     /**
@@ -4680,7 +4694,7 @@
                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
-                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
 
         // Ran jobs up to the job limit. All of them should be allowed to run.
         for (int i = 0; i < mQcConstants.MAX_SESSION_COUNT_PER_RATE_LIMITING_WINDOW; ++i) {
@@ -4695,13 +4709,13 @@
             }
             advanceElapsedClock(SECOND_IN_MILLIS);
             synchronized (mQuotaController.mLock) {
-                mQuotaController.maybeStopTrackingJobLocked(job, null, false);
+                mQuotaController.maybeStopTrackingJobLocked(job, null);
             }
             advanceElapsedClock(SECOND_IN_MILLIS);
         }
         // Start alarm shouldn't have been scheduled since the app was in quota up until this point.
         verify(mAlarmManager, timeout(1000).times(0)).setWindow(
-                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
 
         // The app is now out of session count quota
         JobStatus throttledJob = createJobStatus(
@@ -4721,7 +4735,7 @@
         final long expectedWorkingAlarmTime = stats.sessionRateLimitExpirationTimeElapsed;
         verify(mAlarmManager, timeout(1000).times(1)).setWindow(
                 anyInt(), eq(expectedWorkingAlarmTime), anyLong(),
-                eq(TAG_QUOTA_CHECK), any(), any());
+                eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
     }
 
     @Test
@@ -5185,7 +5199,8 @@
                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
         inOrder.verify(mAlarmManager, timeout(1000).times(0))
-                .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(),
+                        any(Handler.class));
 
         // Test with timing sessions out of window.
         final long now = JobSchedulerService.sElapsedRealtimeClock.millis();
@@ -5196,7 +5211,8 @@
                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
         inOrder.verify(mAlarmManager, timeout(1000).times(0))
-                .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(),
+                        any(Handler.class));
 
         // Test with timing sessions in window but still in quota.
         final long end = now - (22 * HOUR_IN_MILLIS - 5 * MINUTE_IN_MILLIS);
@@ -5208,7 +5224,8 @@
                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
         inOrder.verify(mAlarmManager, timeout(1000).times(0))
-                .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(),
+                        any(Handler.class));
 
         // Add some more sessions, but still in quota.
         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
@@ -5220,7 +5237,8 @@
                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
         inOrder.verify(mAlarmManager, timeout(1000).times(0))
-                .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(),
+                        any(Handler.class));
 
         // Test when out of quota.
         mQuotaController.saveTimingSession(SOURCE_USER_ID, SOURCE_PACKAGE,
@@ -5230,7 +5248,8 @@
                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
         inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
-                anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(),
+                any(Handler.class));
 
         // Alarm already scheduled, so make sure it's not scheduled again.
         synchronized (mQuotaController.mLock) {
@@ -5238,7 +5257,8 @@
                     SOURCE_USER_ID, SOURCE_PACKAGE, standbyBucket);
         }
         inOrder.verify(mAlarmManager, timeout(1000).times(0))
-                .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(),
+                        any(Handler.class));
     }
 
     /** Tests that the start alarm is properly rescheduled if the app's bucket is changed. */
@@ -5282,7 +5302,8 @@
                     SOURCE_USER_ID, SOURCE_PACKAGE, ACTIVE_INDEX);
         }
         inOrder.verify(mAlarmManager, timeout(1000).times(0))
-                .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(),
+                        any(Handler.class));
         inOrder.verify(mAlarmManager, timeout(1000).times(0))
                 .cancel(any(AlarmManager.OnAlarmListener.class));
 
@@ -5297,7 +5318,7 @@
         }
         inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
                 anyInt(), eq(expectedWorkingAlarmTime), anyLong(),
-                eq(TAG_QUOTA_CHECK), any(), any());
+                eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
 
         setStandbyBucket(FREQUENT_INDEX);
         final long expectedFrequentAlarmTime =
@@ -5308,7 +5329,7 @@
         }
         inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
                 anyInt(), eq(expectedFrequentAlarmTime), anyLong(),
-                eq(TAG_QUOTA_CHECK), any(), any());
+                eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
 
         setStandbyBucket(RARE_INDEX);
         final long expectedRareAlarmTime =
@@ -5319,7 +5340,8 @@
                     SOURCE_USER_ID, SOURCE_PACKAGE, RARE_INDEX);
         }
         inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
-                anyInt(), eq(expectedRareAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                anyInt(), eq(expectedRareAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(),
+                any(Handler.class));
 
         // And back up again.
         setStandbyBucket(FREQUENT_INDEX);
@@ -5329,7 +5351,7 @@
         }
         inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
                 anyInt(), eq(expectedFrequentAlarmTime), anyLong(),
-                eq(TAG_QUOTA_CHECK), any(), any());
+                eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
 
         setStandbyBucket(WORKING_INDEX);
         synchronized (mQuotaController.mLock) {
@@ -5338,7 +5360,7 @@
         }
         inOrder.verify(mAlarmManager, timeout(1000).times(1)).setWindow(
                 anyInt(), eq(expectedWorkingAlarmTime), anyLong(),
-                eq(TAG_QUOTA_CHECK), any(), any());
+                eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
 
         setStandbyBucket(ACTIVE_INDEX);
         synchronized (mQuotaController.mLock) {
@@ -5346,7 +5368,8 @@
                     SOURCE_USER_ID, SOURCE_PACKAGE, ACTIVE_INDEX);
         }
         inOrder.verify(mAlarmManager, timeout(1000).times(0))
-                .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(),
+                        any(Handler.class));
         inOrder.verify(mAlarmManager, timeout(1000).times(1))
                 .cancel(any(AlarmManager.OnAlarmListener.class));
     }
@@ -5388,7 +5411,8 @@
                     SOURCE_USER_ID, SOURCE_PACKAGE, WORKING_INDEX);
         }
         verify(mAlarmManager, timeout(1000).times(1)).setWindow(
-                anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(),
+                any(Handler.class));
     }
 
     /** Tests that TimingSessions aren't saved when the device is charging. */
@@ -5408,7 +5432,7 @@
         }
         advanceElapsedClock(5 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(jobStatus, null);
         }
         assertNull(mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
     }
@@ -5434,7 +5458,7 @@
         }
         advanceElapsedClock(5 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(jobStatus, null);
         }
         expected.add(createTimingSession(start, 5 * SECOND_IN_MILLIS, 1));
         assertEquals(expected,
@@ -5464,7 +5488,7 @@
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(jobStatus, null);
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
@@ -5472,11 +5496,11 @@
         }
         advanceElapsedClock(20 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null);
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null);
         }
         expected.add(createTimingSession(start, MINUTE_IN_MILLIS, 3));
         assertEquals(expected,
@@ -5521,7 +5545,7 @@
         long start = JobSchedulerService.sElapsedRealtimeClock.millis();
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobStatus, jobStatus, true);
+            mQuotaController.maybeStopTrackingJobLocked(jobStatus, jobStatus);
         }
         expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
         assertEquals(expected,
@@ -5553,18 +5577,18 @@
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null);
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null);
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         setDischarging();
         start = JobSchedulerService.sElapsedRealtimeClock.millis();
         advanceElapsedClock(20 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(jobStatus, null);
         }
         expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 1));
         assertEquals(expected,
@@ -5583,7 +5607,7 @@
         setCharging();
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null);
         }
         assertEquals(expected,
                 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
@@ -5610,7 +5634,7 @@
         }
         advanceElapsedClock(5 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(jobStatus, null);
         }
         expected.add(createTimingSession(start, 5 * SECOND_IN_MILLIS, 1));
         assertEquals(expected,
@@ -5640,7 +5664,7 @@
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(jobStatus, null);
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
@@ -5648,11 +5672,11 @@
         }
         advanceElapsedClock(20 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(jobStatus3, null);
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(jobStatus2, null);
         }
         expected.add(createTimingSession(start, MINUTE_IN_MILLIS, 3));
         assertEquals(expected,
@@ -5680,7 +5704,7 @@
         setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
         advanceElapsedClock(5 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobStatus, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(jobStatus, null);
         }
         assertNull(mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
     }
@@ -5715,7 +5739,7 @@
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true);
+            mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1);
         }
         expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
         assertEquals(expected,
@@ -5741,11 +5765,11 @@
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobFg3, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(jobFg3, null);
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(jobBg2, null);
         }
         assertEquals(expected,
                 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
@@ -5775,7 +5799,7 @@
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true);
+            mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1);
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS); // UID "inactive" now
         start = JobSchedulerService.sElapsedRealtimeClock.millis();
@@ -5785,11 +5809,11 @@
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobFg3, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(jobFg3, null);
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(jobBg2, null);
         }
         expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 2));
         assertEquals(expected,
@@ -5825,7 +5849,7 @@
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true);
+            mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1);
         }
         expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
         assertEquals(expected,
@@ -5851,11 +5875,11 @@
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobTop, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(jobTop, null);
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(jobBg2, null);
         }
         assertEquals(expected,
                 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
@@ -5887,7 +5911,7 @@
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1, true);
+            mQuotaController.maybeStopTrackingJobLocked(jobBg1, jobBg1);
         }
         advanceElapsedClock(5 * SECOND_IN_MILLIS);
         setProcessState(ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
@@ -5904,12 +5928,12 @@
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobTop, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(jobTop, null);
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobBg2, null, false);
-            mQuotaController.maybeStopTrackingJobLocked(jobFg1, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(jobBg2, null);
+            mQuotaController.maybeStopTrackingJobLocked(jobFg1, null);
         }
         expected.add(createTimingSession(start, 20 * SECOND_IN_MILLIS, 2));
         assertEquals(expected,
@@ -5946,7 +5970,7 @@
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(job1, job1, true);
+            mQuotaController.maybeStopTrackingJobLocked(job1, job1);
         }
         expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
         assertEquals(expected,
@@ -5963,7 +5987,7 @@
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(job2, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(job2, null);
         }
         assertEquals(expected,
                 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
@@ -5981,7 +6005,7 @@
         long elapsedGracePeriodMs = 2 * SECOND_IN_MILLIS;
         advanceElapsedClock(elapsedGracePeriodMs);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(job3, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(job3, null);
         }
         assertEquals(expected,
                 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
@@ -6005,7 +6029,7 @@
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(job4, job4, true);
+            mQuotaController.maybeStopTrackingJobLocked(job4, job4);
         }
         assertEquals(expected,
                 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
@@ -6019,7 +6043,7 @@
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(job5, job5, true);
+            mQuotaController.maybeStopTrackingJobLocked(job5, job5);
         }
         expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
         assertEquals(expected,
@@ -6049,7 +6073,7 @@
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(job, job, true);
+            mQuotaController.maybeStopTrackingJobLocked(job, job);
         }
         expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
         assertEquals(expected,
@@ -6066,7 +6090,7 @@
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(job, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(job, null);
         }
         expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
         assertEquals(expected,
@@ -6085,7 +6109,7 @@
         long elapsedGracePeriodMs = 2 * SECOND_IN_MILLIS;
         advanceElapsedClock(elapsedGracePeriodMs);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(job, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(job, null);
         }
         expected.add(createTimingSession(start, 12 * SECOND_IN_MILLIS, 1));
         assertEquals(expected,
@@ -6110,7 +6134,7 @@
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS + remainingGracePeriod, 1));
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(job, job, true);
+            mQuotaController.maybeStopTrackingJobLocked(job, job);
         }
         assertEquals(expected,
                 mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
@@ -6124,7 +6148,7 @@
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(job, job, true);
+            mQuotaController.maybeStopTrackingJobLocked(job, job);
         }
         expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
         assertEquals(expected,
@@ -6169,7 +6193,7 @@
         // Wait for the grace period to expire so the handler can process the message.
         Thread.sleep(gracePeriodMs);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(job1, job1, true);
+            mQuotaController.maybeStopTrackingJobLocked(job1, job1);
         }
         assertNull(mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
 
@@ -6189,7 +6213,7 @@
         // Wait for the grace period to expire so the handler can process the message.
         Thread.sleep(gracePeriodMs);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(job2, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(job2, null);
         }
         assertNull(mQuotaController.getEJTimingSessions(SOURCE_USER_ID, SOURCE_PACKAGE));
 
@@ -6215,7 +6239,7 @@
         Thread.sleep(2 * gracePeriodMs);
         advanceElapsedClock(gracePeriodMs);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(job3, job3, true);
+            mQuotaController.maybeStopTrackingJobLocked(job3, job3);
         }
         expected.add(createTimingSession(start, gracePeriodMs, 1));
         assertEquals(expected,
@@ -6243,7 +6267,7 @@
         Thread.sleep(2 * gracePeriodMs);
         advanceElapsedClock(gracePeriodMs);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(job4, job4, true);
+            mQuotaController.maybeStopTrackingJobLocked(job4, job4);
         }
         expected.add(createTimingSession(start, gracePeriodMs, 1));
         assertEquals(expected,
@@ -6270,7 +6294,7 @@
         Thread.sleep(2 * gracePeriodMs);
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(job5, job5, true);
+            mQuotaController.maybeStopTrackingJobLocked(job5, job5);
         }
         expected.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
         assertEquals(expected,
@@ -6424,7 +6448,7 @@
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobReg1, jobReg1, true);
+            mQuotaController.maybeStopTrackingJobLocked(jobReg1, jobReg1);
         }
         expectedRegular.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
         assertEquals(expectedRegular,
@@ -6440,7 +6464,7 @@
         }
         advanceElapsedClock(10 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobEJ1, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(jobEJ1, null);
         }
         expectedEJ.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
         assertEquals(expectedRegular,
@@ -6461,12 +6485,12 @@
         }
         advanceElapsedClock(5 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobEJ2, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(jobEJ2, null);
         }
         expectedEJ.add(createTimingSession(start, 10 * SECOND_IN_MILLIS, 1));
         advanceElapsedClock(5 * SECOND_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(jobReg2, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(jobReg2, null);
         }
         expectedRegular.add(
                 createTimingSession(start + 5 * SECOND_IN_MILLIS, 10 * SECOND_IN_MILLIS, 1));
@@ -6556,7 +6580,7 @@
         }
         advanceElapsedClock(5000);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(regJob, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(regJob, null);
         }
         assertEquals(0, debit.getTallyLocked());
         assertEquals(10 * MINUTE_IN_MILLIS,
@@ -6570,7 +6594,7 @@
         }
         advanceElapsedClock(5 * MINUTE_IN_MILLIS);
         synchronized (mQuotaController.mLock) {
-            mQuotaController.maybeStopTrackingJobLocked(eJob, null, false);
+            mQuotaController.maybeStopTrackingJobLocked(eJob, null);
         }
         assertEquals(5 * MINUTE_IN_MILLIS, debit.getTallyLocked());
         assertEquals(5 * MINUTE_IN_MILLIS,
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/StateControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/StateControllerTest.java
index 612e906..51d641b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/StateControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/StateControllerTest.java
@@ -81,8 +81,7 @@
         public void maybeStartTrackingJobLocked(JobStatus jobStatus, JobStatus lastJob) {
         }
 
-        public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob,
-                boolean forUpdate) {
+        public void maybeStopTrackingJobLocked(JobStatus jobStatus, JobStatus incomingJob) {
         }
 
         public void dumpControllerStateLocked(IndentingPrintWriter pw,
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java b/services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java
index aa95916..f88e18b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/restrictions/ThermalStatusRestrictionTest.java
@@ -184,8 +184,10 @@
         when(mJobSchedulerService.isCurrentlyRunningLocked(jobLowPriorityRunning)).thenReturn(true);
         when(mJobSchedulerService.isCurrentlyRunningLocked(jobHighPriorityRunning))
                 .thenReturn(true);
-        when(mJobSchedulerService.isLongRunningLocked(jobLowPriorityRunningLong)).thenReturn(true);
-        when(mJobSchedulerService.isLongRunningLocked(jobHighPriorityRunningLong)).thenReturn(true);
+        when(mJobSchedulerService.isJobInOvertimeLocked(jobLowPriorityRunningLong))
+                .thenReturn(true);
+        when(mJobSchedulerService.isJobInOvertimeLocked(jobHighPriorityRunningLong))
+                .thenReturn(true);
 
         assertFalse(mThermalStatusRestriction.isJobRestricted(jobMinPriority));
         assertFalse(mThermalStatusRestriction.isJobRestricted(jobLowPriority));
diff --git a/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java
similarity index 64%
rename from services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java
rename to services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java
index a7739ed..aabec22 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ApexManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2019 The Android Open Source Project
+ * Copyright (C) 2022 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.
@@ -31,25 +31,29 @@
 import static org.mockito.Mockito.when;
 import static org.testng.Assert.assertThrows;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.apex.ApexInfo;
 import android.apex.ApexSessionInfo;
 import android.apex.ApexSessionParams;
 import android.apex.IApexService;
-import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Build;
 import android.os.Environment;
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.filters.SmallTest;
-import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.pm.parsing.PackageParser2;
-import com.android.server.pm.parsing.TestPackageParser2;
 import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -60,88 +64,139 @@
 import java.io.InputStream;
 import java.io.OutputStream;
 import java.util.List;
+import java.util.Objects;
 
 @SmallTest
 @Presubmit
 @RunWith(AndroidJUnit4.class)
 
 public class ApexManagerTest {
+
+    @Rule
+    public final MockSystemRule mMockSystem = new MockSystemRule();
+
     private static final String TEST_APEX_PKG = "com.android.apex.test";
     private static final String TEST_APEX_FILE_NAME = "apex.test.apex";
     private static final int TEST_SESSION_ID = 99999999;
     private static final int[] TEST_CHILD_SESSION_ID = {8888, 7777};
     private ApexManager mApexManager;
-    private Context mContext;
     private PackageParser2 mPackageParser2;
 
     private IApexService mApexService = mock(IApexService.class);
 
+    private PackageManagerService mPmService;
+
+    private InstallPackageHelper mInstallPackageHelper;
+
     @Before
-    public void setUp() throws RemoteException {
-        mContext = InstrumentationRegistry.getInstrumentation().getContext();
+    public void setUp() throws Exception {
         ApexManager.ApexManagerImpl managerImpl = spy(new ApexManager.ApexManagerImpl());
         doReturn(mApexService).when(managerImpl).waitForApexService();
+        when(mApexService.getActivePackages()).thenReturn(new ApexInfo[0]);
         mApexManager = managerImpl;
-        mPackageParser2 = new TestPackageParser2();
+        mPackageParser2 = new PackageParser2(null, null, null, new PackageParser2.Callback() {
+            @Override
+            public boolean isChangeEnabled(long changeId, @NonNull ApplicationInfo appInfo) {
+                return true;
+            }
+
+            @Override
+            public boolean hasFeature(String feature) {
+                return true;
+            }
+        });
+
+        mMockSystem.system().stageNominalSystemState();
+        mPmService = new PackageManagerService(mMockSystem.mocks().getInjector(),
+                false /*factoryTest*/,
+                MockSystem.Companion.getDEFAULT_VERSION_INFO().fingerprint,
+                false /*isEngBuild*/,
+                false /*isUserDebugBuild*/,
+                Build.VERSION_CODES.CUR_DEVELOPMENT,
+                Build.VERSION.INCREMENTAL);
+        mMockSystem.system().validateFinalState();
+        mInstallPackageHelper = new InstallPackageHelper(mPmService, mock(AppDataHelper.class));
+    }
+
+    @NonNull
+    private List<ApexManager.ScanResult> scanApexInfos(ApexInfo[] apexInfos) {
+        return mInstallPackageHelper.scanApexPackages(apexInfos,
+                ParsingPackageUtils.PARSE_IS_SYSTEM_DIR,
+                PackageManagerService.SCAN_AS_SYSTEM, mPackageParser2,
+                ParallelPackageParser.makeExecutorService());
+    }
+
+    @Nullable
+    private ApexManager.ScanResult findActive(@NonNull List<ApexManager.ScanResult> results) {
+        return results.stream()
+                .filter(it -> it.apexInfo.isActive)
+                .filter(it -> Objects.equals(it.packageName, TEST_APEX_PKG))
+                .findFirst()
+                .orElse(null);
+    }
+
+    @Nullable
+    private ApexManager.ScanResult findFactory(@NonNull List<ApexManager.ScanResult> results,
+            @NonNull String packageName) {
+        return results.stream()
+                .filter(it -> it.apexInfo.isFactory)
+                .filter(it -> Objects.equals(it.packageName, packageName))
+                .findFirst()
+                .orElse(null);
+    }
+
+    @NonNull
+    private AndroidPackage mockParsePackage(@NonNull PackageParser2 parser,
+            @NonNull ApexInfo apexInfo) {
+        var flags = PackageManager.GET_META_DATA | PackageManager.GET_SIGNING_CERTIFICATES;
+        try {
+            var parsedPackage = parser.parsePackage(new File(apexInfo.modulePath), flags,
+                    /* useCaches= */ false);
+            ScanPackageUtils.applyPolicy(parsedPackage,
+                    PackageManagerService.SCAN_AS_APEX | PackageManagerService.SCAN_AS_SYSTEM,
+                    mPmService.getPlatformPackage(), /* isUpdatedSystemApp */ false);
+            // isUpdatedSystemApp is ignoreable above, only used for shared library adjustment
+            return parsedPackage.hideAsFinal();
+        } catch (PackageManagerException e) {
+            throw new RuntimeException(e);
+        }
     }
 
     @Test
-    public void testGetPackageInfo_setFlagsMatchActivePackage() throws RemoteException {
-        ApexInfo[] apexInfo = createApexInfoForTestPkg(true, false);
-        ApexPackageInfo apexPackageInfo = new ApexPackageInfo();
-        apexPackageInfo.scanApexPackages(
-                apexInfo, mPackageParser2, ParallelPackageParser.makeExecutorService());
-        final var activePair = apexPackageInfo.getPackageInfo(TEST_APEX_PKG,
-                ApexManager.MATCH_ACTIVE_PACKAGE);
+    public void testScanActivePackage() {
+        var apexInfos = createApexInfoForTestPkg(true, false);
+        var results = scanApexInfos(apexInfos);
+        var active = findActive(results);
+        var factory = findFactory(results, TEST_APEX_PKG);
 
-        assertThat(activePair).isNotNull();
-        assertThat(activePair.second.getPackageName()).contains(TEST_APEX_PKG);
+        assertThat(active).isNotNull();
+        assertThat(active.packageName).isEqualTo(TEST_APEX_PKG);
 
-        final var factoryPair = apexPackageInfo.getPackageInfo(TEST_APEX_PKG,
-                ApexManager.MATCH_FACTORY_PACKAGE);
-
-        assertThat(factoryPair).isNull();
+        assertThat(factory).isNull();
     }
 
     @Test
-    public void testGetPackageInfo_setFlagsMatchFactoryPackage() throws RemoteException {
-        ApexInfo[] apexInfo = createApexInfoForTestPkg(false, true);
-        ApexPackageInfo apexPackageInfo = new ApexPackageInfo();
-        apexPackageInfo.scanApexPackages(
-                apexInfo, mPackageParser2, ParallelPackageParser.makeExecutorService());
-        var factoryPair = apexPackageInfo.getPackageInfo(TEST_APEX_PKG,
-                ApexManager.MATCH_FACTORY_PACKAGE);
+    public void testScanFactoryPackage() {
+        var apexInfos = createApexInfoForTestPkg(false, true);
+        var results = scanApexInfos(apexInfos);
+        var active = findActive(results);
+        var factory = findFactory(results, TEST_APEX_PKG);
 
-        assertThat(factoryPair).isNotNull();
-        assertThat(factoryPair.second.getPackageName()).contains(TEST_APEX_PKG);
+        assertThat(factory).isNotNull();
+        assertThat(factory.packageName).contains(TEST_APEX_PKG);
 
-        final var activePair = apexPackageInfo.getPackageInfo(TEST_APEX_PKG,
-                ApexManager.MATCH_ACTIVE_PACKAGE);
-
-        assertThat(activePair).isNull();
+        assertThat(active).isNull();
     }
 
     @Test
-    public void testGetPackageInfo_setFlagsNone() throws RemoteException {
-        ApexInfo[] apexInfo = createApexInfoForTestPkg(false, true);
-        ApexPackageInfo apexPackageInfo = new ApexPackageInfo();
-        apexPackageInfo.scanApexPackages(
-                apexInfo, mPackageParser2, ParallelPackageParser.makeExecutorService());
-
-        assertThat(apexPackageInfo.getPackageInfo(TEST_APEX_PKG, 0)).isNull();
-    }
-
-    @Test
-    public void testGetApexSystemServices() throws RemoteException {
-        ApexInfo[] apexInfo = new ApexInfo[] {
+    public void testGetApexSystemServices() {
+        ApexInfo[] apexInfo = new ApexInfo[]{
                 createApexInfoForTestPkg(false, true, 1),
                 // only active apex reports apex-system-service
                 createApexInfoForTestPkg(true, false, 2),
         };
 
-        ApexPackageInfo apexPackageInfo = new ApexPackageInfo();
-        List<ApexManager.ScanResult> scanResults = apexPackageInfo.scanApexPackages(
-                apexInfo, mPackageParser2, ParallelPackageParser.makeExecutorService());
+        List<ApexManager.ScanResult> scanResults = scanApexInfos(apexInfo);
         mApexManager.notifyScanResult(scanResults);
 
         List<ApexSystemServiceInfo> services = mApexManager.getApexSystemServices();
@@ -151,73 +206,11 @@
     }
 
     @Test
-    public void testGetActivePackages() throws RemoteException {
-        ApexInfo[] apexInfo = createApexInfoForTestPkg(true, true);
-        ApexPackageInfo apexPackageInfo = new ApexPackageInfo();
-        apexPackageInfo.scanApexPackages(
-                apexInfo, mPackageParser2, ParallelPackageParser.makeExecutorService());
-
-        assertThat(apexPackageInfo.getActivePackages()).isNotEmpty();
-    }
-
-    @Test
-    public void testGetActivePackages_noneActivePackages() throws RemoteException {
-        ApexInfo[] apexInfo = createApexInfoForTestPkg(false, true);
-        ApexPackageInfo apexPackageInfo = new ApexPackageInfo();
-        apexPackageInfo.scanApexPackages(
-                apexInfo, mPackageParser2, ParallelPackageParser.makeExecutorService());
-
-        assertThat(apexPackageInfo.getActivePackages()).isEmpty();
-    }
-
-    @Test
-    public void testGetFactoryPackages() throws RemoteException {
-        ApexInfo [] apexInfo = createApexInfoForTestPkg(false, true);
-        ApexPackageInfo apexPackageInfo = new ApexPackageInfo();
-        apexPackageInfo.scanApexPackages(
-                apexInfo, mPackageParser2, ParallelPackageParser.makeExecutorService());
-
-        assertThat(apexPackageInfo.getFactoryPackages()).isNotEmpty();
-    }
-
-    @Test
-    public void testGetFactoryPackages_noneFactoryPackages() throws RemoteException {
-        ApexInfo[] apexInfo = createApexInfoForTestPkg(true, false);
-        ApexPackageInfo apexPackageInfo = new ApexPackageInfo();
-        apexPackageInfo.scanApexPackages(
-                apexInfo, mPackageParser2, ParallelPackageParser.makeExecutorService());
-
-        assertThat(apexPackageInfo.getFactoryPackages()).isEmpty();
-    }
-
-    @Test
-    public void testGetInactivePackages() throws RemoteException {
-        ApexInfo[] apexInfo = createApexInfoForTestPkg(false, true);
-        ApexPackageInfo apexPackageInfo = new ApexPackageInfo();
-        apexPackageInfo.scanApexPackages(
-                apexInfo, mPackageParser2, ParallelPackageParser.makeExecutorService());
-
-        assertThat(apexPackageInfo.getInactivePackages()).isNotEmpty();
-    }
-
-    @Test
-    public void testGetInactivePackages_noneInactivePackages() throws RemoteException {
-        ApexInfo[] apexInfo = createApexInfoForTestPkg(true, false);
-        ApexPackageInfo apexPackageInfo = new ApexPackageInfo();
-        apexPackageInfo.scanApexPackages(
-                apexInfo, mPackageParser2, ParallelPackageParser.makeExecutorService());
-
-        assertThat(apexPackageInfo.getInactivePackages()).isEmpty();
-    }
-
-    @Test
-    public void testIsApexPackage() throws RemoteException {
-        ApexInfo[] apexInfo = createApexInfoForTestPkg(false, true);
-        ApexPackageInfo apexPackageInfo = new ApexPackageInfo();
-        apexPackageInfo.scanApexPackages(
-                apexInfo, mPackageParser2, ParallelPackageParser.makeExecutorService());
-
-        assertThat(apexPackageInfo.isApexPackage(TEST_APEX_PKG)).isTrue();
+    public void testIsApexPackage() {
+        var apexInfos = createApexInfoForTestPkg(false, true);
+        var results = scanApexInfos(apexInfos);
+        var factory = findFactory(results, TEST_APEX_PKG);
+        assertThat(factory.pkg.isApex()).isTrue();
     }
 
     @Test
@@ -328,16 +321,14 @@
         assertThat(activeApex.apexModuleName).isEqualTo(TEST_APEX_PKG);
 
         ApexInfo[] apexInfo = createApexInfoForTestPkg(true, true);
-        ApexPackageInfo apexPackageInfo = new ApexPackageInfo();
-        List<ApexManager.ScanResult> scanResults = apexPackageInfo.scanApexPackages(
-                apexInfo, mPackageParser2, ParallelPackageParser.makeExecutorService());
+        List<ApexManager.ScanResult> scanResults = scanApexInfos(apexInfo);
         mApexManager.notifyScanResult(scanResults);
 
         assertThat(mApexManager.getApkInApexInstallError(activeApex.apexModuleName)).isNull();
         mApexManager.reportErrorWithApkInApex(activeApex.apexDirectory.getAbsolutePath(),
                 "Some random error");
         assertThat(mApexManager.getApkInApexInstallError(activeApex.apexModuleName))
-            .isEqualTo("Some random error");
+                .isEqualTo("Some random error");
     }
 
     /**
@@ -357,9 +348,7 @@
         when(fakeApkInApex.getPackageName()).thenReturn("randomPackageName");
 
         ApexInfo[] apexInfo = createApexInfoForTestPkg(true, true);
-        ApexPackageInfo apexPackageInfo = new ApexPackageInfo();
-        List<ApexManager.ScanResult> scanResults = apexPackageInfo.scanApexPackages(
-                apexInfo, mPackageParser2, ParallelPackageParser.makeExecutorService());
+        List<ApexManager.ScanResult> scanResults = scanApexInfos(apexInfo);
         mApexManager.notifyScanResult(scanResults);
 
         assertThat(mApexManager.getApksInApex(activeApex.apexModuleName)).isEmpty();
@@ -371,11 +360,9 @@
     public void testInstallPackage_activeOnSystem() throws Exception {
         ApexInfo activeApexInfo = createApexInfo("test.apex_rebootless", 1, /* isActive= */ true,
                 /* isFactory= */ true, extractResource("test.apex_rebootless_v1",
-                  "test.rebootless_apex_v1.apex"));
+                        "test.rebootless_apex_v1.apex"));
         ApexInfo[] apexInfo = new ApexInfo[]{activeApexInfo};
-        ApexPackageInfo apexPackageInfo = new ApexPackageInfo();
-        apexPackageInfo.scanApexPackages(
-                apexInfo, mPackageParser2, ParallelPackageParser.makeExecutorService());
+        var results = scanApexInfos(apexInfo);
 
         File finalApex = extractResource("test.rebootles_apex_v2", "test.rebootless_apex_v2.apex");
         ApexInfo newApexInfo = createApexInfo("test.apex_rebootless", 2, /* isActive= */ true,
@@ -384,32 +371,28 @@
 
         File installedApex = extractResource("installed", "test.rebootless_apex_v2.apex");
         newApexInfo = mApexManager.installPackage(installedApex);
-        apexPackageInfo.notifyPackageInstalled(newApexInfo, mPackageParser2);
 
-        var newInfo = apexPackageInfo.getPackageInfo("test.apex.rebootless",
-                ApexManager.MATCH_ACTIVE_PACKAGE);
-        assertThat(newInfo.second.getBaseApkPath()).isEqualTo(finalApex.getAbsolutePath());
-        assertThat(newInfo.second.getLongVersionCode()).isEqualTo(2);
+        var newPkg = mockParsePackage(mPackageParser2, newApexInfo);
+        assertThat(newPkg.getBaseApkPath()).isEqualTo(finalApex.getAbsolutePath());
+        assertThat(newPkg.getLongVersionCode()).isEqualTo(2);
 
-        var factoryInfo = apexPackageInfo.getPackageInfo("test.apex.rebootless",
-                ApexManager.MATCH_FACTORY_PACKAGE);
-        assertThat(factoryInfo.second.getBaseApkPath()).isEqualTo(activeApexInfo.modulePath);
-        assertThat(factoryInfo.second.getLongVersionCode()).isEqualTo(1);
-        assertThat(factoryInfo.second.isSystem()).isTrue();
+        var factoryPkg = mockParsePackage(mPackageParser2,
+                findFactory(results, "test.apex.rebootless").apexInfo);
+        assertThat(factoryPkg.getBaseApkPath()).isEqualTo(activeApexInfo.modulePath);
+        assertThat(factoryPkg.getLongVersionCode()).isEqualTo(1);
+        assertThat(factoryPkg.isSystem()).isTrue();
     }
 
     @Test
     public void testInstallPackage_activeOnData() throws Exception {
         ApexInfo factoryApexInfo = createApexInfo("test.apex_rebootless", 1, /* isActive= */ false,
                 /* isFactory= */ true, extractResource("test.apex_rebootless_v1",
-                  "test.rebootless_apex_v1.apex"));
+                        "test.rebootless_apex_v1.apex"));
         ApexInfo activeApexInfo = createApexInfo("test.apex_rebootless", 1, /* isActive= */ true,
                 /* isFactory= */ false, extractResource("test.apex.rebootless@1",
-                  "test.rebootless_apex_v1.apex"));
+                        "test.rebootless_apex_v1.apex"));
         ApexInfo[] apexInfo = new ApexInfo[]{factoryApexInfo, activeApexInfo};
-        ApexPackageInfo apexPackageInfo = new ApexPackageInfo();
-        apexPackageInfo.scanApexPackages(
-                apexInfo, mPackageParser2, ParallelPackageParser.makeExecutorService());
+        var results = scanApexInfos(apexInfo);
 
         File finalApex = extractResource("test.rebootles_apex_v2", "test.rebootless_apex_v2.apex");
         ApexInfo newApexInfo = createApexInfo("test.apex_rebootless", 2, /* isActive= */ true,
@@ -418,30 +401,20 @@
 
         File installedApex = extractResource("installed", "test.rebootless_apex_v2.apex");
         newApexInfo = mApexManager.installPackage(installedApex);
-        apexPackageInfo.notifyPackageInstalled(newApexInfo, mPackageParser2);
 
-        var newInfo = apexPackageInfo.getPackageInfo("test.apex.rebootless",
-                ApexManager.MATCH_ACTIVE_PACKAGE);
-        assertThat(newInfo.second.getBaseApkPath()).isEqualTo(finalApex.getAbsolutePath());
-        assertThat(newInfo.second.getLongVersionCode()).isEqualTo(2);
+        var newPkg = mockParsePackage(mPackageParser2, newApexInfo);
+        assertThat(newPkg.getBaseApkPath()).isEqualTo(finalApex.getAbsolutePath());
+        assertThat(newPkg.getLongVersionCode()).isEqualTo(2);
 
-        var factoryInfo = apexPackageInfo.getPackageInfo("test.apex.rebootless",
-                ApexManager.MATCH_FACTORY_PACKAGE);
-        assertThat(factoryInfo.second.getBaseApkPath()).isEqualTo(factoryApexInfo.modulePath);
-        assertThat(factoryInfo.second.getLongVersionCode()).isEqualTo(1);
-        assertThat(factoryInfo.second.isSystem()).isTrue();
+        var factoryPkg = mockParsePackage(mPackageParser2,
+                findFactory(results, "test.apex.rebootless").apexInfo);
+        assertThat(factoryPkg.getBaseApkPath()).isEqualTo(factoryApexInfo.modulePath);
+        assertThat(factoryPkg.getLongVersionCode()).isEqualTo(1);
+        assertThat(factoryPkg.isSystem()).isTrue();
     }
 
     @Test
     public void testInstallPackageBinderCallFails() throws Exception {
-        ApexInfo activeApexInfo = createApexInfo("test.apex_rebootless", 1, /* isActive= */ true,
-                /* isFactory= */ false, extractResource("test.apex_rebootless_v1",
-                  "test.rebootless_apex_v1.apex"));
-        ApexInfo[] apexInfo = new ApexInfo[]{activeApexInfo};
-        ApexPackageInfo apexPackageInfo = new ApexPackageInfo();
-        apexPackageInfo.scanApexPackages(
-                apexInfo, mPackageParser2, ParallelPackageParser.makeExecutorService());
-
         when(mApexService.installAndActivatePackage(anyString())).thenThrow(
                 new RuntimeException("install failed :("));
 
@@ -452,14 +425,12 @@
     }
 
     @Test
-    public void testGetActivePackageNameForApexModuleName() throws Exception {
+    public void testGetActivePackageNameForApexModuleName() {
         final String moduleName = "com.android.module_name";
 
         ApexInfo[] apexInfo = createApexInfoForTestPkg(true, false);
         apexInfo[0].moduleName = moduleName;
-        ApexPackageInfo apexPackageInfo = new ApexPackageInfo();
-        List<ApexManager.ScanResult> scanResults = apexPackageInfo.scanApexPackages(
-                apexInfo, mPackageParser2, ParallelPackageParser.makeExecutorService());
+        List<ApexManager.ScanResult> scanResults = scanApexInfos(apexInfo);
         mApexManager.notifyScanResult(scanResults);
 
         assertThat(mApexManager.getActivePackageNameForApexModuleName(moduleName))
@@ -472,12 +443,13 @@
         when(mApexService.getActivePackages()).thenReturn(new ApexInfo[]{apex});
 
         final File backingApexFile = mApexManager.getBackingApexFile(
-                new File("/apex/" + TEST_APEX_PKG + "/apk/App/App.apk"));
+                new File(mMockSystem.system().getApexDirectory(),
+                        TEST_APEX_PKG + "/apk/App/App.apk"));
         assertThat(backingApexFile.getAbsolutePath()).isEqualTo(apex.modulePath);
     }
 
     @Test
-    public void testGetBackingApexFile_fileNotOnApexMountPoint_returnsNull() throws Exception {
+    public void testGetBackingApexFile_fileNotOnApexMountPoint_returnsNull() {
         File result = mApexManager.getBackingApexFile(
                 new File("/data/local/tmp/whatever/does-not-matter"));
         assertThat(result).isNull();
@@ -489,22 +461,23 @@
         when(mApexService.getActivePackages()).thenReturn(new ApexInfo[]{apex});
 
         final File backingApexFile = mApexManager.getBackingApexFile(
-                new File("/apex/com.wrong.apex/apk/App"));
+                new File(mMockSystem.system().getApexDirectory(), "com.wrong.apex/apk/App"));
         assertThat(backingApexFile).isNull();
     }
 
     @Test
-    public void testGetBackingApexFiles_topLevelApexDir_returnsNull() throws Exception {
+    public void testGetBackingApexFiles_topLevelApexDir_returnsNull() {
         assertThat(mApexManager.getBackingApexFile(Environment.getApexDirectory())).isNull();
         assertThat(mApexManager.getBackingApexFile(new File("/apex/"))).isNull();
         assertThat(mApexManager.getBackingApexFile(new File("/apex//"))).isNull();
     }
 
     @Test
-    public void testGetBackingApexFiles_flattenedApex() throws Exception {
+    public void testGetBackingApexFiles_flattenedApex() {
         ApexManager flattenedApexManager = new ApexManager.ApexManagerFlattenedApex();
         final File backingApexFile = flattenedApexManager.getBackingApexFile(
-                new File("/apex/com.android.apex.cts.shim/app/CtsShim/CtsShim.apk"));
+                new File(mMockSystem.system().getApexDirectory(),
+                        "com.android.apex.cts.shim/app/CtsShim/CtsShim.apk"));
         assertThat(backingApexFile).isNull();
     }
 
@@ -521,7 +494,7 @@
     }
 
     private ApexInfo createApexInfoForTestPkg(boolean isActive, boolean isFactory, int version) {
-        File apexFile = extractResource(TEST_APEX_PKG,  TEST_APEX_FILE_NAME);
+        File apexFile = extractResource(TEST_APEX_PKG, TEST_APEX_FILE_NAME);
         ApexInfo apexInfo = new ApexInfo();
         apexInfo.isActive = isActive;
         apexInfo.isFactory = isFactory;
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java
index 47f449c..1be7e2e 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/BackgroundDexOptServiceUnitTest.java
@@ -223,7 +223,7 @@
                 /* expectedReschedule= */ false, /* expectedStatus= */ STATUS_OK,
                 /* totalJobFinishedWithParams= */ 1, /* expectedSkippedPackage= */ null);
         runFullJob(mJobServiceForIdle, mJobParametersForIdle,
-                /* expectedReschedule= */ true, /* expectedStatus= */ STATUS_OK,
+                /* expectedReschedule= */ false, /* expectedStatus= */ STATUS_OK,
                 /* totalJobFinishedWithParams= */ 1, /* expectedSkippedPackage= */ null);
     }
 
@@ -241,7 +241,7 @@
         assertThat(getFailedPackageNamesSecondary()).isEmpty();
 
         runFullJob(mJobServiceForIdle, mJobParametersForIdle,
-                /* expectedReschedule= */ true, /* expectedStatus= */ STATUS_OK,
+                /* expectedReschedule= */ false, /* expectedStatus= */ STATUS_OK,
                 /* totalJobFinishedWithParams= */ 1, /* expectedSkippedPackage= */ PACKAGE_AAA);
 
         assertThat(getFailedPackageNamesPrimary()).containsExactly(PACKAGE_AAA);
@@ -256,7 +256,7 @@
         mDexOptResultForPackageAAA = PackageDexOptimizer.DEX_OPT_PERFORMED;
 
         runFullJob(mJobServiceForIdle, mJobParametersForIdle,
-                /* expectedReschedule= */ true, /* expectedStatus= */ STATUS_OK,
+                /* expectedReschedule= */ false, /* expectedStatus= */ STATUS_OK,
                 /* totalJobFinishedWithParams= */ 2, /* expectedSkippedPackage= */ null);
 
         assertThat(getFailedPackageNamesPrimary()).isEmpty();
@@ -393,7 +393,7 @@
         mCancelThread.join(TEST_WAIT_TIMEOUT_MS);
 
         // Always reschedule for periodic job
-        verify(mJobServiceForIdle).jobFinished(mJobParametersForIdle, true);
+        verify(mJobServiceForIdle).jobFinished(mJobParametersForIdle, false);
         verifyLastControlDexOptBlockingCall(false);
     }
 
@@ -421,7 +421,7 @@
         mCancelThread.join(TEST_WAIT_TIMEOUT_MS);
 
         // Always reschedule for periodic job
-        verify(mJobServiceForIdle).jobFinished(mJobParametersForIdle, true);
+        verify(mJobServiceForIdle).jobFinished(mJobParametersForIdle, false);
         verify(mDexOptHelper, never()).controlDexOptBlocking(true);
     }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/InitAppsHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/InitAppsHelperTest.kt
index 2165301..15b4975 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/InitAppsHelperTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/InitAppsHelperTest.kt
@@ -83,7 +83,7 @@
         val pms = createPackageManagerService()
         assertThat(pms.isFirstBoot).isEqualTo(true)
         assertThat(pms.isDeviceUpgrading).isEqualTo(false)
-        val initAppsHelper = InitAppsHelper(pms, rule.mocks().apexManager, null, null,
+        val initAppsHelper = InitAppsHelper(pms, rule.mocks().apexManager, null,
             listOf<ScanPartition>())
         assertThat(
             initAppsHelper.systemScanFlags and PackageManagerService.SCAN_FIRST_BOOT_OR_UPGRADE)
@@ -98,7 +98,7 @@
         val pms = createPackageManagerService()
         assertThat(pms.isFirstBoot).isEqualTo(false)
         assertThat(pms.isDeviceUpgrading).isEqualTo(true)
-        val initAppsHelper = InitAppsHelper(pms, rule.mocks().apexManager, null, null,
+        val initAppsHelper = InitAppsHelper(pms, rule.mocks().apexManager, null,
             listOf<ScanPartition>())
         assertThat(
             initAppsHelper.systemScanFlags and PackageManagerService.SCAN_FIRST_BOOT_OR_UPGRADE)
@@ -112,7 +112,7 @@
         val pms = createPackageManagerService()
         assertThat(pms.isFirstBoot).isEqualTo(false)
         assertThat(pms.isDeviceUpgrading).isEqualTo(false)
-        val initAppsHelper = InitAppsHelper(pms, rule.mocks().apexManager, null, null,
+        val initAppsHelper = InitAppsHelper(pms, rule.mocks().apexManager, null,
             listOf<ScanPartition>())
         assertThat(
             initAppsHelper.systemScanFlags and PackageManagerService.SCAN_FIRST_BOOT_OR_UPGRADE)
@@ -126,7 +126,7 @@
         val pms = createPackageManagerService()
         assertThat(pms.isFirstBoot).isEqualTo(false)
         assertThat(pms.isDeviceUpgrading).isEqualTo(true)
-        val initAppsHelper = InitAppsHelper(pms, rule.mocks().apexManager, null, null,
+        val initAppsHelper = InitAppsHelper(pms, rule.mocks().apexManager, null,
             listOf<ScanPartition>())
         assertThat(
             initAppsHelper.systemScanFlags and PackageManagerService.SCAN_FIRST_BOOT_OR_UPGRADE)
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index dc7bcd6..dd6c733 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -78,14 +78,6 @@
 import com.android.server.testutils.nullable
 import com.android.server.testutils.whenever
 import com.android.server.utils.WatchedArrayMap
-import java.io.File
-import java.io.IOException
-import java.nio.file.Files
-import java.security.PublicKey
-import java.security.cert.CertificateException
-import java.util.Arrays
-import java.util.Random
-import java.util.concurrent.FutureTask
 import libcore.util.HexEncoding
 import org.junit.Assert
 import org.junit.rules.TestRule
@@ -94,6 +86,14 @@
 import org.mockito.AdditionalMatchers.or
 import org.mockito.Mockito
 import org.mockito.quality.Strictness
+import java.io.File
+import java.io.IOException
+import java.nio.file.Files
+import java.security.PublicKey
+import java.security.cert.CertificateException
+import java.util.Arrays
+import java.util.Random
+import java.util.concurrent.FutureTask
 
 /**
  * A utility for mocking behavior of the system and dependencies when testing PackageManagerService
@@ -104,6 +104,9 @@
 class MockSystem(withSession: (StaticMockitoSessionBuilder) -> Unit = {}) {
     private val random = Random()
     val mocks = Mocks()
+
+    // TODO: getBackingApexFile does not handle paths that aren't /apex
+    val apexDirectory = File("/apex")
     val packageCacheDirectory: File =
             Files.createTempDirectory("packageCache").toFile()
     val rootDirectory: File =
@@ -297,7 +300,9 @@
         whenever(mocks.systemConfig.sharedLibraries).thenReturn(DEFAULT_SHARED_LIBRARIES_LIST)
         whenever(mocks.systemConfig.defaultVrComponents).thenReturn(ArraySet())
         whenever(mocks.systemConfig.hiddenApiWhitelistedApps).thenReturn(ArraySet())
+        wheneverStatic { SystemProperties.set(anyString(), anyString()) }.thenDoNothing()
         wheneverStatic { SystemProperties.getBoolean("fw.free_cache_v2", true) }.thenReturn(true)
+        wheneverStatic { Environment.getApexDirectory() }.thenReturn(apexDirectory)
         wheneverStatic { Environment.getPackageCacheDirectory() }.thenReturn(packageCacheDirectory)
         wheneverStatic { SystemProperties.digestOf("ro.build.fingerprint") }.thenReturn("cacheName")
         wheneverStatic { Environment.getRootDirectory() }.thenReturn(rootDirectory)
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerInternalTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerInternalTest.java
deleted file mode 100644
index 278e04a..0000000
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerInternalTest.java
+++ /dev/null
@@ -1,258 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.server.pm;
-
-import static android.os.UserHandle.USER_SYSTEM;
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.Display.INVALID_DISPLAY;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import static org.junit.Assert.assertThrows;
-
-import android.util.Log;
-
-import org.junit.Test;
-
-/**
- * Run as {@code atest FrameworksMockingServicesTests:com.android.server.pm.UserManagerInternalTest}
- */
-public final class UserManagerInternalTest extends UserManagerServiceOrInternalTestCase {
-
-    private static final String TAG = UserManagerInternalTest.class.getSimpleName();
-
-    // NOTE: most the tests below only apply to MUMD configurations, so we're not adding _mumd_
-    // in the test names, but _nonMumd_ instead
-
-    @Test
-    public void testAssignUserToDisplay_nonMumd_defaultDisplayIgnored() {
-        mUmi.assignUserToDisplay(USER_ID, DEFAULT_DISPLAY);
-
-        assertNoUserAssignedToDisplay();
-    }
-
-    @Test
-    public void testAssignUserToDisplay_nonMumd_otherDisplay_currentUser() {
-        mockCurrentUser(USER_ID);
-
-        assertThrows(UnsupportedOperationException.class,
-                () -> mUmi.assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID));
-    }
-
-    @Test
-    public void testAssignUserToDisplay_nonMumd_otherDisplay_startProfileOfcurrentUser() {
-        mockCurrentUser(PARENT_USER_ID);
-        addDefaultProfileAndParent();
-        startDefaultProfile();
-
-        assertThrows(UnsupportedOperationException.class,
-                () -> mUmi.assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID));
-    }
-
-    @Test
-    public void testAssignUserToDisplay_nonMumd_otherDisplay_stoppedProfileOfcurrentUser() {
-        mockCurrentUser(PARENT_USER_ID);
-        addDefaultProfileAndParent();
-        stopDefaultProfile();
-
-        assertThrows(UnsupportedOperationException.class,
-                () -> mUmi.assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID));
-    }
-
-    @Test
-    public void testAssignUserToDisplay_defaultDisplayIgnored() {
-        enableUsersOnSecondaryDisplays();
-
-        mUmi.assignUserToDisplay(USER_ID, DEFAULT_DISPLAY);
-
-        assertNoUserAssignedToDisplay();
-    }
-
-    @Test
-    public void testAssignUserToDisplay_systemUser() {
-        enableUsersOnSecondaryDisplays();
-
-        assertThrows(IllegalArgumentException.class,
-                () -> mUmi.assignUserToDisplay(USER_SYSTEM, SECONDARY_DISPLAY_ID));
-    }
-
-    @Test
-    public void testAssignUserToDisplay_invalidDisplay() {
-        enableUsersOnSecondaryDisplays();
-
-        assertThrows(IllegalArgumentException.class,
-                () -> mUmi.assignUserToDisplay(USER_ID, INVALID_DISPLAY));
-    }
-
-    @Test
-    public void testAssignUserToDisplay_currentUser() {
-        enableUsersOnSecondaryDisplays();
-        mockCurrentUser(USER_ID);
-
-        assertThrows(IllegalArgumentException.class,
-                () -> mUmi.assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID));
-
-        assertNoUserAssignedToDisplay();
-    }
-
-    @Test
-    public void testAssignUserToDisplay_startedProfileOfCurrentUser() {
-        enableUsersOnSecondaryDisplays();
-        mockCurrentUser(PARENT_USER_ID);
-        addDefaultProfileAndParent();
-        startDefaultProfile();
-
-        IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
-                () -> mUmi.assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID));
-
-        Log.v(TAG, "Exception: " + e);
-        assertNoUserAssignedToDisplay();
-    }
-
-    @Test
-    public void testAssignUserToDisplay_stoppedProfileOfCurrentUser() {
-        enableUsersOnSecondaryDisplays();
-        mockCurrentUser(PARENT_USER_ID);
-        addDefaultProfileAndParent();
-        stopDefaultProfile();
-
-        IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
-                () -> mUmi.assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID));
-
-        Log.v(TAG, "Exception: " + e);
-        assertNoUserAssignedToDisplay();
-    }
-
-    @Test
-    public void testAssignUserToDisplay_displayAvailable() {
-        enableUsersOnSecondaryDisplays();
-
-        mUmi.assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
-
-        assertUserAssignedToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
-    }
-
-    @Test
-    public void testAssignUserToDisplay_displayAlreadyAssigned() {
-        enableUsersOnSecondaryDisplays();
-
-        mUmi.assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
-
-        IllegalStateException e = assertThrows(IllegalStateException.class,
-                () -> mUmi.assignUserToDisplay(OTHER_USER_ID, SECONDARY_DISPLAY_ID));
-
-        Log.v(TAG, "Exception: " + e);
-        assertWithMessage("exception (%s) message", e).that(e).hasMessageThat()
-                .matches("Cannot.*" + OTHER_USER_ID + ".*" + SECONDARY_DISPLAY_ID + ".*already.*"
-                        + USER_ID + ".*");
-    }
-
-    @Test
-    public void testAssignUserToDisplay_userAlreadyAssigned() {
-        enableUsersOnSecondaryDisplays();
-
-        mUmi.assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
-
-        IllegalStateException e = assertThrows(IllegalStateException.class,
-                () -> mUmi.assignUserToDisplay(USER_ID, OTHER_SECONDARY_DISPLAY_ID));
-
-        Log.v(TAG, "Exception: " + e);
-        assertWithMessage("exception (%s) message", e).that(e).hasMessageThat()
-                .matches("Cannot.*" + USER_ID + ".*" + OTHER_SECONDARY_DISPLAY_ID + ".*already.*"
-                        + SECONDARY_DISPLAY_ID + ".*");
-
-        assertUserAssignedToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
-    }
-
-    @Test
-    public void testAssignUserToDisplay_profileOnSameDisplayAsParent() {
-        enableUsersOnSecondaryDisplays();
-        addDefaultProfileAndParent();
-
-        mUmi.assignUserToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
-        IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
-                () -> mUmi.assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID));
-
-        Log.v(TAG, "Exception: " + e);
-        assertUserAssignedToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
-    }
-
-    @Test
-    public void testAssignUserToDisplay_profileOnDifferentDisplayAsParent() {
-        enableUsersOnSecondaryDisplays();
-        addDefaultProfileAndParent();
-
-        mUmi.assignUserToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
-        IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
-                () -> mUmi.assignUserToDisplay(PROFILE_USER_ID, OTHER_SECONDARY_DISPLAY_ID));
-
-        Log.v(TAG, "Exception: " + e);
-        assertUserAssignedToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
-    }
-
-    @Test
-    public void testAssignUserToDisplay_profileDefaultDisplayParentOnSecondaryDisplay() {
-        enableUsersOnSecondaryDisplays();
-        addDefaultProfileAndParent();
-
-        mUmi.assignUserToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
-        IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
-                () -> mUmi.assignUserToDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY));
-
-        Log.v(TAG, "Exception: " + e);
-        assertUserAssignedToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
-    }
-
-    @Test
-    public void testUnassignUserFromDisplay_nonMumd_ignored() {
-        mockCurrentUser(USER_ID);
-
-        mUmi.unassignUserFromDisplay(USER_SYSTEM);
-        mUmi.unassignUserFromDisplay(USER_ID);
-        mUmi.unassignUserFromDisplay(OTHER_USER_ID);
-
-        assertNoUserAssignedToDisplay();
-    }
-
-    @Test
-    public void testUnassignUserFromDisplay() {
-        testAssignUserToDisplay_displayAvailable();
-
-        mUmi.unassignUserFromDisplay(USER_ID);
-
-        assertNoUserAssignedToDisplay();
-    }
-
-    @Override
-    protected boolean isUserVisible(int userId) {
-        return mUmi.isUserVisible(userId);
-    }
-
-    @Override
-    protected boolean isUserVisibleOnDisplay(int userId, int displayId) {
-        return mUmi.isUserVisible(userId, displayId);
-    }
-
-    @Override
-    protected int getDisplayAssignedToUser(int userId) {
-        return mUmi.getDisplayAssignedToUser(userId);
-    }
-
-    @Override
-    protected int getUserAssignedToDisplay(int displayId) {
-        return mUmi.getUserAssignedToDisplay(displayId);
-    }
-}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceOrInternalTestCase.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceOrInternalTestCase.java
index 90a5fa0..6c85b7a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceOrInternalTestCase.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceOrInternalTestCase.java
@@ -15,10 +15,6 @@
  */
 package com.android.server.pm;
 
-import static android.os.UserHandle.USER_NULL;
-import static android.view.Display.DEFAULT_DISPLAY;
-import static android.view.Display.INVALID_DISPLAY;
-
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 
@@ -34,7 +30,6 @@
 import android.os.UserManager;
 import android.util.Log;
 import android.util.SparseArray;
-import android.util.SparseIntArray;
 
 import androidx.test.annotation.UiThreadTest;
 
@@ -46,12 +41,8 @@
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Test;
 import org.mockito.Mock;
 
-import java.util.LinkedHashMap;
-import java.util.Map;
-
 /**
  * Base class for {@link UserManagerInternalTest} and {@link UserManagerInternalTest}.
  *
@@ -59,9 +50,13 @@
  * "symbiotic relationship - some methods of the former simply call the latter and vice versa.
  *
  * <p>Ideally, only one of them should have the logic, but since that's not the case, this class
- * provices the infra to make it easier to test both (which in turn would make it easier / safer to
+ * provides the infra to make it easier to test both (which in turn would make it easier / safer to
  * refactor their logic later).
  */
+// TODO(b/244644281): there is no UserManagerInternalTest anymore as the logic being tested there
+// moved to UserVisibilityController, so it might be simpler to merge this class into
+// UserManagerServiceTest (once the UserVisibilityController -> UserManagerService dependency is
+// fixed)
 abstract class UserManagerServiceOrInternalTestCase extends ExtendedMockitoTestCase {
 
     private static final String TAG = UserManagerServiceOrInternalTestCase.class.getSimpleName();
@@ -90,31 +85,12 @@
      */
     protected static final int PROFILE_USER_ID = 643;
 
-    /**
-     * Id of a secondary display (i.e, not {@link android.view.Display.DEFAULT_DISPLAY}).
-     */
-    protected static final int SECONDARY_DISPLAY_ID = 42;
-
-    /**
-     * Id of another secondary display (i.e, not {@link android.view.Display.DEFAULT_DISPLAY}).
-     */
-    protected static final int OTHER_SECONDARY_DISPLAY_ID = 108;
-
     private final Object mPackagesLock = new Object();
     private final Context mRealContext = androidx.test.InstrumentationRegistry.getInstrumentation()
             .getTargetContext();
     private final SparseArray<UserData> mUsers = new SparseArray<>();
 
-    // TODO(b/244644281): manipulating mUsersOnSecondaryDisplays directly leaks implementation
-    // details into the unit test, but it's fine for now - in the long term, this logic should be
-    // refactored into a proper UserDisplayAssignment class.
-    private final SparseIntArray mUsersOnSecondaryDisplays = new SparseIntArray();
-
     private Context mSpiedContext;
-    private UserManagerService mStandardUms;
-    private UserManagerService mMumdUms;
-    private UserManagerInternal mStandardUmi;
-    private UserManagerInternal mMumdUmi;
 
     private @Mock PackageManagerService mMockPms;
     private @Mock UserDataPreparer mMockUserDataPreparer;
@@ -122,17 +98,11 @@
 
     /**
      * Reference to the {@link UserManagerService} being tested.
-     *
-     * <p>By default, such service doesn't support {@code MUMD} (Multiple Users on Multiple
-     * Displays), but that can be changed by calling {@link #enableUsersOnSecondaryDisplays()}.
      */
     protected UserManagerService mUms;
 
     /**
      * Reference to the {@link UserManagerInternal} being tested.
-     *
-     * <p>By default, such service doesn't support {@code MUMD} (Multiple Users on Multiple
-     * Displays), but that can be changed by calling {@link #enableUsersOnSecondaryDisplays()}.
      */
     protected UserManagerInternal mUmi;
 
@@ -151,32 +121,12 @@
         // Called when WatchedUserStates is constructed
         doNothing().when(() -> UserManager.invalidateIsUserUnlockedCache());
 
-        // Need to set both UserManagerService instances here, as they need to be run in the
-        // UiThread
-
-        // mMumdUms / mMumdUmi
-        mockIsUsersOnSecondaryDisplaysEnabled(/* usersOnSecondaryDisplaysEnabled= */ true);
-        mMumdUms = new UserManagerService(mSpiedContext, mMockPms, mMockUserDataPreparer,
-                mPackagesLock, mRealContext.getDataDir(), mUsers, mUsersOnSecondaryDisplays);
-        assertWithMessage("UserManagerService.isUsersOnSecondaryDisplaysEnabled()")
-                .that(mMumdUms.isUsersOnSecondaryDisplaysEnabled())
-                .isTrue();
-        mMumdUmi = LocalServices.getService(UserManagerInternal.class);
-        assertWithMessage("LocalServices.getService(UserManagerInternal.class)").that(mMumdUmi)
+        // Must construct UserManagerService in the UiThread
+        mUms = new UserManagerService(mSpiedContext, mMockPms, mMockUserDataPreparer,
+                mPackagesLock, mRealContext.getDataDir(), mUsers);
+        mUmi = LocalServices.getService(UserManagerInternal.class);
+        assertWithMessage("LocalServices.getService(UserManagerInternal.class)").that(mUmi)
                 .isNotNull();
-        resetUserManagerInternal();
-
-        // mStandardUms / mStandardUmi
-        mockIsUsersOnSecondaryDisplaysEnabled(/* usersOnSecondaryDisplaysEnabled= */ false);
-        mStandardUms = new UserManagerService(mSpiedContext, mMockPms, mMockUserDataPreparer,
-                mPackagesLock, mRealContext.getDataDir(), mUsers, mUsersOnSecondaryDisplays);
-        assertWithMessage("UserManagerService.isUsersOnSecondaryDisplaysEnabled()")
-                .that(mStandardUms.isUsersOnSecondaryDisplaysEnabled())
-                .isFalse();
-        mStandardUmi = LocalServices.getService(UserManagerInternal.class);
-        assertWithMessage("LocalServices.getService(UserManagerInternal.class)").that(mStandardUmi)
-                .isNotNull();
-        setServiceFixtures(/*usersOnSecondaryDisplaysEnabled= */ false);
     }
 
     @After
@@ -185,373 +135,10 @@
         LocalServices.removeServiceForTest(UserManagerInternal.class);
     }
 
-    //////////////////////////////////////////////////////////////////////////////////////////////
-    // Methods whose UMS implementation calls UMI or vice-versa - they're tested in this class, //
-    // but the subclass must provide the proper implementation                                  //
-    //////////////////////////////////////////////////////////////////////////////////////////////
-
-    protected abstract boolean isUserVisible(int userId);
-    protected abstract boolean isUserVisibleOnDisplay(int userId, int displayId);
-    protected abstract int getDisplayAssignedToUser(int userId);
-    protected abstract int getUserAssignedToDisplay(int displayId);
-
-    /////////////////////////////////
-    // Tests for the above methods //
-    /////////////////////////////////
-
-    @Test
-    public void testIsUserVisible_invalidUser() {
-        mockCurrentUser(USER_ID);
-
-        assertWithMessage("isUserVisible(%s)", USER_NULL).that(isUserVisible(USER_NULL)).isFalse();
-    }
-
-    @Test
-    public void testIsUserVisible_currentUser() {
-        mockCurrentUser(USER_ID);
-
-        assertWithMessage("isUserVisible(%s)", USER_ID).that(isUserVisible(USER_ID)).isTrue();
-    }
-
-    @Test
-    public void testIsUserVisible_nonCurrentUser() {
-        mockCurrentUser(OTHER_USER_ID);
-
-        assertWithMessage("isUserVisible(%s)", USER_ID).that(isUserVisible(USER_ID)).isFalse();
-    }
-
-    @Test
-    public void testIsUserVisible_startedProfileOfcurrentUser() {
-        addDefaultProfileAndParent();
-        mockCurrentUser(PARENT_USER_ID);
-        startDefaultProfile();
-        setUserState(PROFILE_USER_ID, UserState.STATE_RUNNING_UNLOCKED);
-
-        assertWithMessage("isUserVisible(%s)", PROFILE_USER_ID).that(isUserVisible(PROFILE_USER_ID))
-                .isTrue();
-    }
-
-    @Test
-    public void testIsUserVisible_stoppedProfileOfcurrentUser() {
-        addDefaultProfileAndParent();
-        mockCurrentUser(PARENT_USER_ID);
-        stopDefaultProfile();
-
-        assertWithMessage("isUserVisible(%s)", PROFILE_USER_ID).that(isUserVisible(PROFILE_USER_ID))
-                .isFalse();
-    }
-
-    @Test
-    public void testIsUserVisible_bgUserOnSecondaryDisplay() {
-        enableUsersOnSecondaryDisplays();
-        mockCurrentUser(OTHER_USER_ID);
-        assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
-
-        assertWithMessage("isUserVisible(%s)", USER_ID).that(isUserVisible(USER_ID)).isTrue();
-    }
-
-    // NOTE: we don't need to add tests for profiles (started / stopped profiles of bg user), as
-    // isUserVisible() for bg users relies only on the user / display assignments
-
-    @Test
-    public void testIsUserVisibleOnDisplay_invalidUser() {
-        mockCurrentUser(USER_ID);
-
-        assertWithMessage("isUserVisibleOnDisplay(%s, %s)", USER_NULL, DEFAULT_DISPLAY)
-                .that(isUserVisibleOnDisplay(USER_NULL, DEFAULT_DISPLAY)).isFalse();
-    }
-
-    @Test
-    public void testIsUserVisibleOnDisplay_currentUserInvalidDisplay() {
-        mockCurrentUser(USER_ID);
-
-        assertWithMessage("isUserVisibleOnDisplay(%s, %s)", USER_ID, INVALID_DISPLAY)
-                .that(isUserVisibleOnDisplay(USER_ID, INVALID_DISPLAY)).isFalse();
-    }
-
-    @Test
-    public void testIsUserVisibleOnDisplay_currentUserDefaultDisplay() {
-        mockCurrentUser(USER_ID);
-
-        assertWithMessage("isUserVisibleOnDisplay(%s, %s)", USER_ID, DEFAULT_DISPLAY)
-                .that(isUserVisibleOnDisplay(USER_ID, DEFAULT_DISPLAY)).isTrue();
-    }
-
-    @Test
-    public void testIsUserVisibleOnDisplay_currentUserSecondaryDisplay() {
-        mockCurrentUser(USER_ID);
-
-        assertWithMessage("isUserVisibleOnDisplay(%s, %s)", USER_ID, SECONDARY_DISPLAY_ID)
-                .that(isUserVisibleOnDisplay(USER_ID, SECONDARY_DISPLAY_ID)).isTrue();
-    }
-
-    @Test
-    public void testIsUserVisibleOnDisplay_mumd_currentUserUnassignedSecondaryDisplay() {
-        enableUsersOnSecondaryDisplays();
-        mockCurrentUser(USER_ID);
-
-        assertWithMessage("isUserVisibleOnDisplay(%s, %s)", USER_ID, SECONDARY_DISPLAY_ID)
-                .that(isUserVisibleOnDisplay(USER_ID, SECONDARY_DISPLAY_ID)).isTrue();
-    }
-
-    @Test
-    public void testIsUserVisibleOnDisplay_mumd_currentUserSecondaryDisplayAssignedToAnotherUser() {
-        enableUsersOnSecondaryDisplays();
-        mockCurrentUser(USER_ID);
-        assignUserToDisplay(OTHER_USER_ID, SECONDARY_DISPLAY_ID);
-
-        assertWithMessage("isUserVisibleOnDisplay(%s, %s)", USER_ID, SECONDARY_DISPLAY_ID)
-                .that(isUserVisibleOnDisplay(USER_ID, SECONDARY_DISPLAY_ID)).isFalse();
-    }
-
-    @Test
-    public void testIsUserVisibleOnDisplay_mumd_startedProfileOfCurrentUserSecondaryDisplayAssignedToAnotherUser() {
-        enableUsersOnSecondaryDisplays();
-        addDefaultProfileAndParent();
-        startDefaultProfile();
-        mockCurrentUser(PARENT_USER_ID);
-        assignUserToDisplay(OTHER_USER_ID, SECONDARY_DISPLAY_ID);
-
-        assertWithMessage("isUserVisibleOnDisplay(%s, %s)", PROFILE_USER_ID, SECONDARY_DISPLAY_ID)
-                .that(isUserVisibleOnDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID)).isFalse();
-    }
-
-    @Test
-    public void testIsUserVisibleOnDisplay_mumd_stoppedProfileOfCurrentUserSecondaryDisplayAssignedToAnotherUser() {
-        enableUsersOnSecondaryDisplays();
-        addDefaultProfileAndParent();
-        stopDefaultProfile();
-        mockCurrentUser(PARENT_USER_ID);
-        assignUserToDisplay(OTHER_USER_ID, SECONDARY_DISPLAY_ID);
-
-        assertWithMessage("isUserVisibleOnDisplay(%s, %s)", PROFILE_USER_ID, SECONDARY_DISPLAY_ID)
-                .that(isUserVisibleOnDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID)).isFalse();
-    }
-
-    @Test
-    public void testIsUserVisibleOnDisplay_nonCurrentUserDefaultDisplay() {
-        mockCurrentUser(OTHER_USER_ID);
-
-        assertWithMessage("isUserVisibleOnDisplay(%s, %s)", USER_ID, DEFAULT_DISPLAY)
-                .that(isUserVisibleOnDisplay(USER_ID, DEFAULT_DISPLAY)).isFalse();
-    }
-
-    @Test
-    public void testIsUserVisibleOnDisplay_startedProfileOfcurrentUserInvalidDisplay() {
-        addDefaultProfileAndParent();
-        mockCurrentUser(PARENT_USER_ID);
-        startDefaultProfile();
-
-        assertWithMessage("isUserVisibleOnDisplay(%s, %s)", PROFILE_USER_ID, INVALID_DISPLAY)
-                .that(isUserVisibleOnDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY)).isTrue();
-    }
-
-    @Test
-    public void testIsUserVisibleOnDisplay_stoppedProfileOfcurrentUserInvalidDisplay() {
-        addDefaultProfileAndParent();
-        mockCurrentUser(PARENT_USER_ID);
-        stopDefaultProfile();
-
-        assertWithMessage("isUserVisibleOnDisplay(%s, %s)", PROFILE_USER_ID, INVALID_DISPLAY)
-                .that(isUserVisibleOnDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY)).isFalse();
-    }
-
-    @Test
-    public void testIsUserVisibleOnDisplay_startedProfileOfcurrentUserDefaultDisplay() {
-        addDefaultProfileAndParent();
-        mockCurrentUser(PARENT_USER_ID);
-        startDefaultProfile();
-        setUserState(PROFILE_USER_ID, UserState.STATE_RUNNING_UNLOCKED);
-
-        assertWithMessage("isUserVisibleOnDisplay(%s, %s)", PROFILE_USER_ID, DEFAULT_DISPLAY)
-                .that(isUserVisibleOnDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY)).isTrue();
-    }
-
-    @Test
-    public void testIsUserVisibleOnDisplay_stoppedProfileOfcurrentUserDefaultDisplay() {
-        addDefaultProfileAndParent();
-        mockCurrentUser(PARENT_USER_ID);
-        stopDefaultProfile();
-
-        assertWithMessage("isUserVisibleOnDisplay(%s, %s)", PROFILE_USER_ID, DEFAULT_DISPLAY)
-                .that(isUserVisibleOnDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY)).isFalse();
-    }
-
-    @Test
-    public void testIsUserVisibleOnDisplay_startedProfileOfcurrentUserSecondaryDisplay() {
-        addDefaultProfileAndParent();
-        mockCurrentUser(PARENT_USER_ID);
-        startDefaultProfile();
-        setUserState(PROFILE_USER_ID, UserState.STATE_RUNNING_UNLOCKED);
-
-        assertWithMessage("isUserVisibleOnDisplay(%s, %s)", PROFILE_USER_ID, SECONDARY_DISPLAY_ID)
-                .that(isUserVisibleOnDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID)).isTrue();
-    }
-
-    @Test
-    public void testIsUserVisibleOnDisplay_stoppedProfileOfcurrentUserSecondaryDisplay() {
-        addDefaultProfileAndParent();
-        mockCurrentUser(PARENT_USER_ID);
-        stopDefaultProfile();
-
-        assertWithMessage("isUserVisibleOnDisplay(%s, %s)", PROFILE_USER_ID, SECONDARY_DISPLAY_ID)
-                .that(isUserVisibleOnDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID)).isFalse();
-    }
-
-    @Test
-    public void testIsUserVisibleOnDisplay_mumd_bgUserOnSecondaryDisplay() {
-        enableUsersOnSecondaryDisplays();
-        mockCurrentUser(OTHER_USER_ID);
-        assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
-
-        assertWithMessage("isUserVisibleOnDisplay(%s, %s)", USER_ID, SECONDARY_DISPLAY_ID)
-                .that(isUserVisibleOnDisplay(USER_ID, SECONDARY_DISPLAY_ID)).isTrue();
-    }
-
-    @Test
-    public void testIsUserVisibleOnDisplay_mumd_bgUserOnAnotherSecondaryDisplay() {
-        enableUsersOnSecondaryDisplays();
-        mockCurrentUser(OTHER_USER_ID);
-        assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
-
-        assertWithMessage("isUserVisibleOnDisplay(%s, %s)", USER_ID, SECONDARY_DISPLAY_ID)
-                .that(isUserVisibleOnDisplay(USER_ID, OTHER_SECONDARY_DISPLAY_ID)).isFalse();
-    }
-
-    // NOTE: we don't need to add tests for profiles (started / stopped profiles of bg user), as
-    // isUserVisibleOnDisplay() for bg users relies only on the user / display assignments
-
-    @Test
-    public void testGetDisplayAssignedToUser_invalidUser() {
-        mockCurrentUser(USER_ID);
-
-        assertWithMessage("getDisplayAssignedToUser(%s)", USER_NULL)
-                .that(getDisplayAssignedToUser(USER_NULL)).isEqualTo(INVALID_DISPLAY);
-    }
-
-    @Test
-    public void testGetDisplayAssignedToUser_currentUser() {
-        mockCurrentUser(USER_ID);
-
-        assertWithMessage("getDisplayAssignedToUser(%s)", USER_ID)
-                .that(getDisplayAssignedToUser(USER_ID)).isEqualTo(DEFAULT_DISPLAY);
-    }
-
-    @Test
-    public void testGetDisplayAssignedToUser_nonCurrentUser() {
-        mockCurrentUser(OTHER_USER_ID);
-
-        assertWithMessage("getDisplayAssignedToUser(%s)", USER_ID)
-                .that(getDisplayAssignedToUser(USER_ID)).isEqualTo(INVALID_DISPLAY);
-    }
-
-    @Test
-    public void testGetDisplayAssignedToUser_startedProfileOfcurrentUser() {
-        addDefaultProfileAndParent();
-        mockCurrentUser(PARENT_USER_ID);
-        startDefaultProfile();
-        setUserState(PROFILE_USER_ID, UserState.STATE_RUNNING_UNLOCKED);
-
-        assertWithMessage("getDisplayAssignedToUser(%s)", PROFILE_USER_ID)
-                .that(getDisplayAssignedToUser(PROFILE_USER_ID)).isEqualTo(DEFAULT_DISPLAY);
-    }
-
-    @Test
-    public void testGetDisplayAssignedToUser_stoppedProfileOfcurrentUser() {
-        addDefaultProfileAndParent();
-        mockCurrentUser(PARENT_USER_ID);
-        stopDefaultProfile();
-
-        assertWithMessage("getDisplayAssignedToUser(%s)", PROFILE_USER_ID)
-                .that(getDisplayAssignedToUser(PROFILE_USER_ID)).isEqualTo(INVALID_DISPLAY);
-    }
-
-    @Test
-    public void testGetDisplayAssignedToUser_bgUserOnSecondaryDisplay() {
-        enableUsersOnSecondaryDisplays();
-        mockCurrentUser(OTHER_USER_ID);
-        assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
-
-        assertWithMessage("getDisplayAssignedToUser(%s)", USER_ID)
-                .that(getDisplayAssignedToUser(USER_ID)).isEqualTo(SECONDARY_DISPLAY_ID);
-    }
-
-    // NOTE: we don't need to add tests for profiles (started / stopped profiles of bg user), as
-    // getDisplayAssignedToUser() for bg users relies only on the user / display assignments
-
-    @Test
-    public void testGetUserAssignedToDisplay_invalidDisplay() {
-        mockCurrentUser(USER_ID);
-
-        assertWithMessage("getUserAssignedToDisplay(%s)", INVALID_DISPLAY)
-                .that(getUserAssignedToDisplay(INVALID_DISPLAY)).isEqualTo(USER_ID);
-    }
-
-    @Test
-    public void testGetUserAssignedToDisplay_defaultDisplay() {
-        mockCurrentUser(USER_ID);
-
-        assertWithMessage("getUserAssignedToDisplay(%s)", DEFAULT_DISPLAY)
-                .that(getUserAssignedToDisplay(DEFAULT_DISPLAY)).isEqualTo(USER_ID);
-    }
-
-    @Test
-    public void testGetUserAssignedToDisplay_secondaryDisplay() {
-        mockCurrentUser(USER_ID);
-
-        assertWithMessage("getUserAssignedToDisplay(%s)", SECONDARY_DISPLAY_ID)
-                .that(getUserAssignedToDisplay(SECONDARY_DISPLAY_ID)).isEqualTo(USER_ID);
-    }
-
-    @Test
-    public void testGetUserAssignedToDisplay_mumd_bgUserOnSecondaryDisplay() {
-        enableUsersOnSecondaryDisplays();
-        mockCurrentUser(OTHER_USER_ID);
-        assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
-
-        assertWithMessage("getUserAssignedToDisplay(%s)", SECONDARY_DISPLAY_ID)
-                .that(getUserAssignedToDisplay(SECONDARY_DISPLAY_ID)).isEqualTo(USER_ID);
-    }
-
-    @Test
-    public void testGetUserAssignedToDisplay_mumd_noUserOnSecondaryDisplay() {
-        enableUsersOnSecondaryDisplays();
-        mockCurrentUser(USER_ID);
-
-        assertWithMessage("getUserAssignedToDisplay(%s)", SECONDARY_DISPLAY_ID)
-                .that(getUserAssignedToDisplay(SECONDARY_DISPLAY_ID)).isEqualTo(USER_ID);
-    }
-
-    // TODO(b/244644281): scenario below shouldn't happen on "real life", as the profile cannot be
-    // started on secondary display if its parent isn't, so we might need to remove (or refactor
-    // this test) if/when the underlying logic changes
-    @Test
-    public void testGetUserAssignedToDisplay_mumd_profileOnSecondaryDisplay() {
-        enableUsersOnSecondaryDisplays();
-        addDefaultProfileAndParent();
-        mockCurrentUser(USER_ID);
-        assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
-
-        assertWithMessage("getUserAssignedToDisplay(%s)", SECONDARY_DISPLAY_ID)
-                .that(getUserAssignedToDisplay(SECONDARY_DISPLAY_ID)).isEqualTo(USER_ID);
-    }
-
-    // NOTE: we don't need to add tests for profiles (started / stopped profiles of bg user), as
-    // getUserAssignedToDisplay() for bg users relies only on the user / display assignments
-
-
     ///////////////////////////////////////////
     // Helper methods exposed to sub-classes //
     ///////////////////////////////////////////
 
-    /**
-     * Change test fixtures to use a version that supports {@code MUMD} (Multiple Users on Multiple
-     * Displays).
-     */
-    protected final void enableUsersOnSecondaryDisplays() {
-        setServiceFixtures(/* usersOnSecondaryDisplaysEnabled= */ true);
-    }
-
     protected final void mockCurrentUser(@UserIdInt int userId) {
         mockGetLocalService(ActivityManagerInternal.class, mActivityManagerInternal);
 
@@ -597,65 +184,19 @@
         setUserState(userId, UserState.STATE_STOPPING);
     }
 
-    // NOTE: should only called by tests that indirectly needs to check user assignments (like
-    // isUserVisible), not by tests for the user assignment methods per se.
-    protected final void assignUserToDisplay(@UserIdInt int userId, int displayId) {
-        mUsersOnSecondaryDisplays.put(userId, displayId);
-    }
-
-    protected final void assertNoUserAssignedToDisplay() {
-        assertWithMessage("mUsersOnSecondaryDisplays()").that(usersOnSecondaryDisplaysAsMap())
-                .isEmpty();
-    }
-
-    protected final void assertUserAssignedToDisplay(@UserIdInt int userId, int displayId) {
-        assertWithMessage("mUsersOnSecondaryDisplays()").that(usersOnSecondaryDisplaysAsMap())
-                .containsExactly(userId, displayId);
+    protected final void setUserState(@UserIdInt int userId, int userState) {
+        mUmi.setUserState(userId, userState);
     }
 
     ///////////////////
     // Private infra //
     ///////////////////
 
-    private void setServiceFixtures(boolean usersOnSecondaryDisplaysEnabled) {
-        Log.d(TAG, "Setting fixtures for usersOnSecondaryDisplaysEnabled="
-                + usersOnSecondaryDisplaysEnabled);
-        if (usersOnSecondaryDisplaysEnabled) {
-            mUms = mMumdUms;
-            mUmi = mMumdUmi;
-        } else {
-            mUms = mStandardUms;
-            mUmi = mStandardUmi;
-        }
-    }
-
-    private void mockIsUsersOnSecondaryDisplaysEnabled(boolean enabled) {
-        Log.d(TAG, "Mocking UserManager.isUsersOnSecondaryDisplaysEnabled() to return " + enabled);
-        doReturn(enabled).when(() -> UserManager.isUsersOnSecondaryDisplaysEnabled());
-    }
-
     private void addUserData(TestUserData userData) {
         Log.d(TAG, "Adding " + userData);
         mUsers.put(userData.info.id, userData);
     }
 
-    private void setUserState(@UserIdInt int userId, int userState) {
-        mUmi.setUserState(userId, userState);
-    }
-
-    private void removeUserState(@UserIdInt int userId) {
-        mUmi.removeUserState(userId);
-    }
-
-    private Map<Integer, Integer> usersOnSecondaryDisplaysAsMap() {
-        int size = mUsersOnSecondaryDisplays.size();
-        Map<Integer, Integer> map = new LinkedHashMap<>(size);
-        for (int i = 0; i < size; i++) {
-            map.put(mUsersOnSecondaryDisplays.keyAt(i), mUsersOnSecondaryDisplays.valueAt(i));
-        }
-        return map;
-    }
-
     private static final class TestUserData extends UserData {
 
         @SuppressWarnings("deprecation")
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
index 8b5921c..b5ffe5f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceTest.java
@@ -123,24 +123,4 @@
         assertWithMessage("isUserRunning(%s)", PROFILE_USER_ID)
                 .that(mUms.isUserRunning(PROFILE_USER_ID)).isFalse();
     }
-
-    @Override
-    protected boolean isUserVisible(int userId) {
-        return mUms.isUserVisibleUnchecked(userId);
-    }
-
-    @Override
-    protected boolean isUserVisibleOnDisplay(int userId, int displayId) {
-        return mUms.isUserVisibleOnDisplay(userId, displayId);
-    }
-
-    @Override
-    protected int getDisplayAssignedToUser(int userId) {
-        return mUms.getDisplayAssignedToUser(userId);
-    }
-
-    @Override
-    protected int getUserAssignedToDisplay(int displayId) {
-        return mUms.getUserAssignedToDisplay(displayId);
-    }
 }
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java
new file mode 100644
index 0000000..21f541f
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorMUMDTest.java
@@ -0,0 +1,303 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.pm;
+
+import static android.os.UserHandle.USER_SYSTEM;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.INVALID_DISPLAY;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import static org.junit.Assert.assertThrows;
+
+import android.util.Log;
+
+import org.junit.Test;
+
+/**
+ * Tests for {@link UserVisibilityMediator} tests for devices that support concurrent Multiple
+ * Users on Multiple Displays (A.K.A {@code MUMD}).
+ *
+ * <p>Run as
+ * {@code atest FrameworksMockingServicesTests:com.android.server.pm.UserVisibilityMediatorMUMDTest}
+ */
+public final class UserVisibilityMediatorMUMDTest extends UserVisibilityMediatorTestCase {
+
+    private static final String TAG = UserVisibilityMediatorMUMDTest.class.getSimpleName();
+
+    public UserVisibilityMediatorMUMDTest() {
+        super(/* usersOnSecondaryDisplaysEnabled= */ true);
+    }
+
+    @Test
+    public void testAssignUserToDisplay_systemUser() {
+        assertThrows(IllegalArgumentException.class,
+                () -> mMediator.assignUserToDisplay(USER_SYSTEM, SECONDARY_DISPLAY_ID));
+    }
+
+    @Test
+    public void testAssignUserToDisplay_invalidDisplay() {
+        assertThrows(IllegalArgumentException.class,
+                () -> mMediator.assignUserToDisplay(USER_ID, INVALID_DISPLAY));
+    }
+
+    @Test
+    public void testAssignUserToDisplay_currentUser() {
+        mockCurrentUser(USER_ID);
+
+        assertThrows(IllegalArgumentException.class,
+                () -> mMediator.assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID));
+
+        assertNoUserAssignedToDisplay();
+    }
+
+    @Test
+    public void testAssignUserToDisplay_startedProfileOfCurrentUser() {
+        mockCurrentUser(PARENT_USER_ID);
+        addDefaultProfileAndParent();
+        startDefaultProfile();
+
+        IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+                () -> mMediator.assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID));
+
+        Log.v(TAG, "Exception: " + e);
+        assertNoUserAssignedToDisplay();
+    }
+
+    @Test
+    public void testAssignUserToDisplay_stoppedProfileOfCurrentUser() {
+        mockCurrentUser(PARENT_USER_ID);
+        addDefaultProfileAndParent();
+        stopDefaultProfile();
+
+        IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+                () -> mMediator.assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID));
+
+        Log.v(TAG, "Exception: " + e);
+        assertNoUserAssignedToDisplay();
+    }
+
+    @Test
+    public void testAssignUserToDisplay_displayAvailable() {
+        mMediator.assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+
+        assertUserAssignedToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+    }
+
+    @Test
+    public void testAssignUserToDisplay_displayAlreadyAssigned() {
+        mMediator.assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+
+        IllegalStateException e = assertThrows(IllegalStateException.class,
+                () -> mMediator.assignUserToDisplay(OTHER_USER_ID, SECONDARY_DISPLAY_ID));
+
+        Log.v(TAG, "Exception: " + e);
+        assertWithMessage("exception (%s) message", e).that(e).hasMessageThat()
+                .matches("Cannot.*" + OTHER_USER_ID + ".*" + SECONDARY_DISPLAY_ID + ".*already.*"
+                        + USER_ID + ".*");
+    }
+
+    @Test
+    public void testAssignUserToDisplay_userAlreadyAssigned() {
+        mMediator.assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+
+        IllegalStateException e = assertThrows(IllegalStateException.class,
+                () -> mMediator.assignUserToDisplay(USER_ID, OTHER_SECONDARY_DISPLAY_ID));
+
+        Log.v(TAG, "Exception: " + e);
+        assertWithMessage("exception (%s) message", e).that(e).hasMessageThat()
+                .matches("Cannot.*" + USER_ID + ".*" + OTHER_SECONDARY_DISPLAY_ID + ".*already.*"
+                        + SECONDARY_DISPLAY_ID + ".*");
+
+        assertUserAssignedToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+    }
+
+    @Test
+    public void testAssignUserToDisplay_profileOnSameDisplayAsParent() {
+        addDefaultProfileAndParent();
+
+        mMediator.assignUserToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
+        IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+                () -> mMediator.assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID));
+
+        Log.v(TAG, "Exception: " + e);
+        assertUserAssignedToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
+    }
+
+    @Test
+    public void testAssignUserToDisplay_profileOnDifferentDisplayAsParent() {
+        addDefaultProfileAndParent();
+
+        mMediator.assignUserToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
+        IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+                () -> mMediator.assignUserToDisplay(PROFILE_USER_ID, OTHER_SECONDARY_DISPLAY_ID));
+
+        Log.v(TAG, "Exception: " + e);
+        assertUserAssignedToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
+    }
+
+    @Test
+    public void testAssignUserToDisplay_profileDefaultDisplayParentOnSecondaryDisplay() {
+        addDefaultProfileAndParent();
+
+        mMediator.assignUserToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
+        IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
+                () -> mMediator.assignUserToDisplay(PROFILE_USER_ID, DEFAULT_DISPLAY));
+
+        Log.v(TAG, "Exception: " + e);
+        assertUserAssignedToDisplay(PARENT_USER_ID, SECONDARY_DISPLAY_ID);
+    }
+
+    @Test
+    public void testUnassignUserFromDisplay() {
+        testAssignUserToDisplay_displayAvailable();
+
+        mMediator.unassignUserFromDisplay(USER_ID);
+
+        assertNoUserAssignedToDisplay();
+    }
+
+    @Test
+    public void testIsUserVisible_bgUserOnSecondaryDisplay() {
+        mockCurrentUser(OTHER_USER_ID);
+        assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+
+        assertWithMessage("isUserVisible(%s)", USER_ID)
+                .that(mMediator.isUserVisible(USER_ID)).isTrue();
+    }
+
+    // NOTE: we don't need to add tests for profiles (started / stopped profiles of bg user), as
+    // isUserVisible() for bg users relies only on the user / display assignments
+
+    @Test
+    public void testIsUserVisibleOnDisplay_currentUserUnassignedSecondaryDisplay() {
+        mockCurrentUser(USER_ID);
+
+        assertWithMessage("isUserVisible(%s, %s)", USER_ID, SECONDARY_DISPLAY_ID)
+                .that(mMediator.isUserVisible(USER_ID, SECONDARY_DISPLAY_ID)).isTrue();
+    }
+
+    @Test
+    public void testIsUserVisibleOnDisplay_currentUserSecondaryDisplayAssignedToAnotherUser() {
+        mockCurrentUser(USER_ID);
+        assignUserToDisplay(OTHER_USER_ID, SECONDARY_DISPLAY_ID);
+
+        assertWithMessage("isUserVisible(%s, %s)", USER_ID, SECONDARY_DISPLAY_ID)
+                .that(mMediator.isUserVisible(USER_ID, SECONDARY_DISPLAY_ID)).isFalse();
+    }
+
+    @Test
+    public void testIsUserVisibleOnDisplay_startedProfileOfCurrentUserSecondaryDisplayAssignedToAnotherUser() {
+        addDefaultProfileAndParent();
+        startDefaultProfile();
+        mockCurrentUser(PARENT_USER_ID);
+        assignUserToDisplay(OTHER_USER_ID, SECONDARY_DISPLAY_ID);
+
+        assertWithMessage("isUserVisible(%s, %s)", PROFILE_USER_ID, SECONDARY_DISPLAY_ID)
+                .that(mMediator.isUserVisible(PROFILE_USER_ID, SECONDARY_DISPLAY_ID)).isFalse();
+    }
+
+    @Test
+    public void testIsUserVisibleOnDisplay_stoppedProfileOfCurrentUserSecondaryDisplayAssignedToAnotherUser() {
+        addDefaultProfileAndParent();
+        stopDefaultProfile();
+        mockCurrentUser(PARENT_USER_ID);
+        assignUserToDisplay(OTHER_USER_ID, SECONDARY_DISPLAY_ID);
+
+        assertWithMessage("isUserVisible(%s, %s)", PROFILE_USER_ID, SECONDARY_DISPLAY_ID)
+                .that(mMediator.isUserVisible(PROFILE_USER_ID, SECONDARY_DISPLAY_ID)).isFalse();
+    }
+
+    @Test
+    public void testIsUserVisibleOnDisplay_startedProfileOfCurrentUserOnUnassignedSecondaryDisplay() {
+        addDefaultProfileAndParent();
+        startDefaultProfile();
+        mockCurrentUser(PARENT_USER_ID);
+
+        // TODO(b/244644281): change it to isFalse() once isUserVisible() is fixed (see note there)
+        assertWithMessage("isUserVisible(%s, %s)", PROFILE_USER_ID, SECONDARY_DISPLAY_ID)
+                .that(mMediator.isUserVisible(PROFILE_USER_ID, SECONDARY_DISPLAY_ID)).isTrue();
+    }
+
+    @Test
+    public void testIsUserVisibleOnDisplay_bgUserOnSecondaryDisplay() {
+        mockCurrentUser(OTHER_USER_ID);
+        assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+
+        assertWithMessage("isUserVisible(%s, %s)", USER_ID, SECONDARY_DISPLAY_ID)
+                .that(mMediator.isUserVisible(USER_ID, SECONDARY_DISPLAY_ID)).isTrue();
+    }
+
+    @Test
+    public void testIsUserVisibleOnDisplay_bgUserOnAnotherSecondaryDisplay() {
+        mockCurrentUser(OTHER_USER_ID);
+        assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+
+        assertWithMessage("isUserVisible(%s, %s)", USER_ID, SECONDARY_DISPLAY_ID)
+                .that(mMediator.isUserVisible(USER_ID, OTHER_SECONDARY_DISPLAY_ID)).isFalse();
+    }
+
+    // NOTE: we don't need to add tests for profiles (started / stopped profiles of bg user), as
+    // the tests for isUserVisible(userId, display) for non-current users relies on the explicit
+    // user / display assignments
+    // TODO(b/244644281): add such tests if the logic change
+
+    @Test
+    public void testGetDisplayAssignedToUser_bgUserOnSecondaryDisplay() {
+        mockCurrentUser(OTHER_USER_ID);
+        assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+
+        assertWithMessage("getDisplayAssignedToUser(%s)", USER_ID)
+                .that(mMediator.getDisplayAssignedToUser(USER_ID))
+                .isEqualTo(SECONDARY_DISPLAY_ID);
+    }
+
+    // NOTE: we don't need to add tests for profiles (started / stopped profiles of bg user), as
+    // getDisplayAssignedToUser() for bg users relies only on the user / display assignments
+
+    @Test
+    public void testGetUserAssignedToDisplay_bgUserOnSecondaryDisplay() {
+        mockCurrentUser(OTHER_USER_ID);
+        assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID);
+
+        assertWithMessage("getUserAssignedToDisplay(%s)", SECONDARY_DISPLAY_ID)
+                .that(mMediator.getUserAssignedToDisplay(SECONDARY_DISPLAY_ID)).isEqualTo(USER_ID);
+    }
+
+    @Test
+    public void testGetUserAssignedToDisplay_noUserOnSecondaryDisplay() {
+        mockCurrentUser(USER_ID);
+
+        assertWithMessage("getUserAssignedToDisplay(%s)", SECONDARY_DISPLAY_ID)
+                .that(mMediator.getUserAssignedToDisplay(SECONDARY_DISPLAY_ID)).isEqualTo(USER_ID);
+    }
+
+    // TODO(b/244644281): scenario below shouldn't happen on "real life", as the profile cannot be
+    // started on secondary display if its parent isn't, so we might need to remove (or refactor
+    // this test) if/when the underlying logic changes
+    @Test
+    public void testGetUserAssignedToDisplay_profileOnSecondaryDisplay() {
+        addDefaultProfileAndParent();
+        mockCurrentUser(USER_ID);
+        assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID);
+
+        assertWithMessage("getUserAssignedToDisplay(%s)", SECONDARY_DISPLAY_ID)
+                .that(mMediator.getUserAssignedToDisplay(SECONDARY_DISPLAY_ID)).isEqualTo(USER_ID);
+    }
+
+    // NOTE: we don't need to add tests for profiles (started / stopped profiles of bg user), as
+    // getUserAssignedToDisplay() for bg users relies only on the user / display assignments
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java
new file mode 100644
index 0000000..7ae8117
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorSUSDTest.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.pm;
+
+import static android.os.UserHandle.USER_SYSTEM;
+
+import static org.junit.Assert.assertThrows;
+
+import org.junit.Test;
+
+/**
+ * Tests for {@link UserVisibilityMediator} tests for devices that DO NOT support concurrent
+ * multiple users on multiple displays (A.K.A {@code SUSD} - Single User on Single Device).
+ *
+ * <p>Run as
+ * {@code atest FrameworksMockingServicesTests:com.android.server.pm.UserVisibilityMediatorSUSDTest}
+ */
+public final class UserVisibilityMediatorSUSDTest extends UserVisibilityMediatorTestCase {
+
+    public UserVisibilityMediatorSUSDTest() {
+        super(/* usersOnSecondaryDisplaysEnabled= */ false);
+    }
+
+    @Test
+    public void testAssignUserToDisplay_otherDisplay_currentUser() {
+        mockCurrentUser(USER_ID);
+
+        assertThrows(UnsupportedOperationException.class,
+                () -> mMediator.assignUserToDisplay(USER_ID, SECONDARY_DISPLAY_ID));
+    }
+
+    @Test
+    public void testAssignUserToDisplay_otherDisplay_startProfileOfcurrentUser() {
+        mockCurrentUser(PARENT_USER_ID);
+        addDefaultProfileAndParent();
+        startDefaultProfile();
+
+        assertThrows(UnsupportedOperationException.class,
+                () -> mMediator.assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID));
+    }
+
+    @Test
+    public void testAssignUserToDisplay_otherDisplay_stoppedProfileOfcurrentUser() {
+        mockCurrentUser(PARENT_USER_ID);
+        addDefaultProfileAndParent();
+        stopDefaultProfile();
+
+        assertThrows(UnsupportedOperationException.class,
+                () -> mMediator.assignUserToDisplay(PROFILE_USER_ID, SECONDARY_DISPLAY_ID));
+    }
+
+    @Test
+    public void testUnassignUserFromDisplay_ignored() {
+        mockCurrentUser(USER_ID);
+
+        mMediator.unassignUserFromDisplay(USER_SYSTEM);
+        mMediator.unassignUserFromDisplay(USER_ID);
+        mMediator.unassignUserFromDisplay(OTHER_USER_ID);
+
+        assertNoUserAssignedToDisplay();
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
new file mode 100644
index 0000000..22e6e0d
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserVisibilityMediatorTestCase.java
@@ -0,0 +1,323 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.pm;
+
+import static android.os.UserHandle.USER_NULL;
+import static android.view.Display.DEFAULT_DISPLAY;
+import static android.view.Display.INVALID_DISPLAY;
+
+import static com.android.server.am.UserState.STATE_RUNNING_UNLOCKED;
+
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import android.annotation.UserIdInt;
+import android.util.SparseIntArray;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+/**
+ * Base class for {@link UserVisibilityMediator} tests.
+ *
+ * <p>It contains common logics and tests for behaviors that should be invariant regardless of the
+ * device mode (for example, whether the device supports concurrent multiple users on multiple
+ * displays or not).
+ */
+abstract class UserVisibilityMediatorTestCase extends UserManagerServiceOrInternalTestCase {
+
+    /**
+     * Id of a secondary display (i.e, not {@link android.view.Display.DEFAULT_DISPLAY}).
+     */
+    protected static final int SECONDARY_DISPLAY_ID = 42;
+
+    /**
+     * Id of another secondary display (i.e, not {@link android.view.Display.DEFAULT_DISPLAY}).
+     */
+    protected static final int OTHER_SECONDARY_DISPLAY_ID = 108;
+
+    private final boolean mUsersOnSecondaryDisplaysEnabled;
+
+    // TODO(b/244644281): manipulating mUsersOnSecondaryDisplays directly leaks implementation
+    // details into the unit test, but it's fine for now as the tests were copied "as is" - it
+    // would be better to use a geter() instead
+    protected final SparseIntArray mUsersOnSecondaryDisplays = new SparseIntArray();
+
+    protected UserVisibilityMediator mMediator;
+
+    protected UserVisibilityMediatorTestCase(boolean usersOnSecondaryDisplaysEnabled) {
+        mUsersOnSecondaryDisplaysEnabled = usersOnSecondaryDisplaysEnabled;
+    }
+
+    @Before
+    public final void setMediator() {
+        mMediator = new UserVisibilityMediator(mUms, mUsersOnSecondaryDisplaysEnabled,
+                mUsersOnSecondaryDisplays);
+    }
+
+    @Test
+    public final void testAssignUserToDisplay_defaultDisplayIgnored() {
+        mMediator.assignUserToDisplay(USER_ID, DEFAULT_DISPLAY);
+
+        assertNoUserAssignedToDisplay();
+    }
+
+    @Test
+    public final void testIsUserVisible_invalidUser() {
+        mockCurrentUser(USER_ID);
+
+        assertWithMessage("isUserVisible(%s)", USER_NULL)
+                .that(mMediator.isUserVisible(USER_NULL)).isFalse();
+    }
+
+    @Test
+    public final void testIsUserVisible_currentUser() {
+        mockCurrentUser(USER_ID);
+
+        assertWithMessage("isUserVisible(%s)", USER_ID)
+                .that(mMediator.isUserVisible(USER_ID)).isTrue();
+    }
+
+    @Test
+    public final void testIsUserVisible_nonCurrentUser() {
+        mockCurrentUser(OTHER_USER_ID);
+
+        assertWithMessage("isUserVisible(%s)", USER_ID)
+                .that(mMediator.isUserVisible(USER_ID)).isFalse();
+    }
+
+    @Test
+    public final void testIsUserVisible_startedProfileOfcurrentUser() {
+        addDefaultProfileAndParent();
+        mockCurrentUser(PARENT_USER_ID);
+        startDefaultProfile();
+        setUserState(PROFILE_USER_ID, STATE_RUNNING_UNLOCKED);
+
+        assertWithMessage("isUserVisible(%s)", PROFILE_USER_ID)
+                .that(mMediator.isUserVisible(PROFILE_USER_ID)).isTrue();
+    }
+
+    @Test
+    public final void testIsUserVisible_stoppedProfileOfcurrentUser() {
+        addDefaultProfileAndParent();
+        mockCurrentUser(PARENT_USER_ID);
+        stopDefaultProfile();
+
+        assertWithMessage("isUserVisible(%s)", PROFILE_USER_ID)
+                .that(mMediator.isUserVisible(PROFILE_USER_ID)).isFalse();
+    }
+
+    @Test
+    public final void testIsUserVisibleOnDisplay_invalidUser() {
+        mockCurrentUser(USER_ID);
+
+        assertWithMessage("isUserVisible(%s, %s)", USER_NULL, DEFAULT_DISPLAY)
+                .that(mMediator.isUserVisible(USER_NULL, DEFAULT_DISPLAY)).isFalse();
+    }
+
+    @Test
+    public final void testIsUserVisibleOnDisplay_currentUserInvalidDisplay() {
+        mockCurrentUser(USER_ID);
+
+        assertWithMessage("isUserVisible(%s, %s)", USER_ID, INVALID_DISPLAY)
+                .that(mMediator.isUserVisible(USER_ID, INVALID_DISPLAY)).isFalse();
+    }
+
+    @Test
+    public final void testIsUserVisibleOnDisplay_currentUserDefaultDisplay() {
+        mockCurrentUser(USER_ID);
+
+        assertWithMessage("isUserVisible(%s, %s)", USER_ID, DEFAULT_DISPLAY)
+                .that(mMediator.isUserVisible(USER_ID, DEFAULT_DISPLAY)).isTrue();
+    }
+
+    @Test
+    public final void testIsUserVisibleOnDisplay_currentUserSecondaryDisplay() {
+        mockCurrentUser(USER_ID);
+
+        assertWithMessage("isUserVisible(%s, %s)", USER_ID, SECONDARY_DISPLAY_ID)
+                .that(mMediator.isUserVisible(USER_ID, SECONDARY_DISPLAY_ID)).isTrue();
+    }
+
+    @Test
+    public final void testIsUserVisibleOnDisplay_nonCurrentUserDefaultDisplay() {
+        mockCurrentUser(OTHER_USER_ID);
+
+        assertWithMessage("isUserVisible(%s, %s)", USER_ID, DEFAULT_DISPLAY)
+                .that(mMediator.isUserVisible(USER_ID, DEFAULT_DISPLAY)).isFalse();
+    }
+
+    @Test
+    public final void testIsUserVisibleOnDisplay_startedProfileOfcurrentUserInvalidDisplay() {
+        addDefaultProfileAndParent();
+        mockCurrentUser(PARENT_USER_ID);
+        startDefaultProfile();
+
+        assertWithMessage("isUserVisible(%s, %s)", PROFILE_USER_ID, INVALID_DISPLAY)
+                .that(mMediator.isUserVisible(PROFILE_USER_ID, DEFAULT_DISPLAY)).isTrue();
+    }
+
+    @Test
+    public final void testIsUserVisibleOnDisplay_stoppedProfileOfcurrentUserInvalidDisplay() {
+        addDefaultProfileAndParent();
+        mockCurrentUser(PARENT_USER_ID);
+        stopDefaultProfile();
+
+        assertWithMessage("isUserVisible(%s, %s)", PROFILE_USER_ID, INVALID_DISPLAY)
+                .that(mMediator.isUserVisible(PROFILE_USER_ID, DEFAULT_DISPLAY)).isFalse();
+    }
+
+    @Test
+    public final void testIsUserVisibleOnDisplay_startedProfileOfcurrentUserDefaultDisplay() {
+        addDefaultProfileAndParent();
+        mockCurrentUser(PARENT_USER_ID);
+        startDefaultProfile();
+        setUserState(PROFILE_USER_ID, STATE_RUNNING_UNLOCKED);
+
+        assertWithMessage("isUserVisible(%s, %s)", PROFILE_USER_ID, DEFAULT_DISPLAY)
+                .that(mMediator.isUserVisible(PROFILE_USER_ID, DEFAULT_DISPLAY)).isTrue();
+    }
+
+    @Test
+    public final void testIsUserVisibleOnDisplay_stoppedProfileOfcurrentUserDefaultDisplay() {
+        addDefaultProfileAndParent();
+        mockCurrentUser(PARENT_USER_ID);
+        stopDefaultProfile();
+
+        assertWithMessage("isUserVisible(%s, %s)", PROFILE_USER_ID, DEFAULT_DISPLAY)
+                .that(mMediator.isUserVisible(PROFILE_USER_ID, DEFAULT_DISPLAY)).isFalse();
+    }
+
+    @Test
+    public final void testIsUserVisibleOnDisplay_startedProfileOfCurrentUserSecondaryDisplay() {
+        addDefaultProfileAndParent();
+        mockCurrentUser(PARENT_USER_ID);
+        startDefaultProfile();
+        setUserState(PROFILE_USER_ID, STATE_RUNNING_UNLOCKED);
+
+        assertWithMessage("isUserVisible(%s, %s)", PROFILE_USER_ID, SECONDARY_DISPLAY_ID)
+                .that(mMediator.isUserVisible(PROFILE_USER_ID, SECONDARY_DISPLAY_ID)).isTrue();
+    }
+
+    @Test
+    public void testIsUserVisibleOnDisplay_stoppedProfileOfcurrentUserSecondaryDisplay() {
+        addDefaultProfileAndParent();
+        mockCurrentUser(PARENT_USER_ID);
+        stopDefaultProfile();
+
+        assertWithMessage("isUserVisible(%s, %s)", PROFILE_USER_ID, SECONDARY_DISPLAY_ID)
+                .that(mMediator.isUserVisible(PROFILE_USER_ID, SECONDARY_DISPLAY_ID)).isFalse();
+    }
+
+    @Test
+    public void testGetDisplayAssignedToUser_invalidUser() {
+        mockCurrentUser(USER_ID);
+
+        assertWithMessage("getDisplayAssignedToUser(%s)", USER_NULL)
+                .that(mMediator.getDisplayAssignedToUser(USER_NULL)).isEqualTo(INVALID_DISPLAY);
+    }
+
+    @Test
+    public void testGetDisplayAssignedToUser_currentUser() {
+        mockCurrentUser(USER_ID);
+
+        assertWithMessage("getDisplayAssignedToUser(%s)", USER_ID)
+                .that(mMediator.getDisplayAssignedToUser(USER_ID)).isEqualTo(DEFAULT_DISPLAY);
+    }
+
+    @Test
+    public final void testGetDisplayAssignedToUser_nonCurrentUser() {
+        mockCurrentUser(OTHER_USER_ID);
+
+        assertWithMessage("getDisplayAssignedToUser(%s)", USER_ID)
+                .that(mMediator.getDisplayAssignedToUser(USER_ID)).isEqualTo(INVALID_DISPLAY);
+    }
+
+    @Test
+    public final void testGetDisplayAssignedToUser_startedProfileOfcurrentUser() {
+        addDefaultProfileAndParent();
+        mockCurrentUser(PARENT_USER_ID);
+        startDefaultProfile();
+        setUserState(PROFILE_USER_ID, STATE_RUNNING_UNLOCKED);
+
+        assertWithMessage("getDisplayAssignedToUser(%s)", PROFILE_USER_ID)
+                .that(mMediator.getDisplayAssignedToUser(PROFILE_USER_ID))
+                .isEqualTo(DEFAULT_DISPLAY);
+    }
+
+    @Test
+    public final void testGetDisplayAssignedToUser_stoppedProfileOfcurrentUser() {
+        addDefaultProfileAndParent();
+        mockCurrentUser(PARENT_USER_ID);
+        stopDefaultProfile();
+
+        assertWithMessage("getDisplayAssignedToUser(%s)", PROFILE_USER_ID)
+                .that(mMediator.getDisplayAssignedToUser(PROFILE_USER_ID))
+                .isEqualTo(INVALID_DISPLAY);
+    }
+
+    @Test
+    public void testGetUserAssignedToDisplay_invalidDisplay() {
+        mockCurrentUser(USER_ID);
+
+        assertWithMessage("getUserAssignedToDisplay(%s)", INVALID_DISPLAY)
+                .that(mMediator.getUserAssignedToDisplay(INVALID_DISPLAY)).isEqualTo(USER_ID);
+    }
+
+    @Test
+    public final void testGetUserAssignedToDisplay_defaultDisplay() {
+        mockCurrentUser(USER_ID);
+
+        assertWithMessage("getUserAssignedToDisplay(%s)", DEFAULT_DISPLAY)
+                .that(mMediator.getUserAssignedToDisplay(DEFAULT_DISPLAY)).isEqualTo(USER_ID);
+    }
+
+    @Test
+    public final void testGetUserAssignedToDisplay_secondaryDisplay() {
+        mockCurrentUser(USER_ID);
+
+        assertWithMessage("getUserAssignedToDisplay(%s)", SECONDARY_DISPLAY_ID)
+                .that(mMediator.getUserAssignedToDisplay(SECONDARY_DISPLAY_ID))
+                .isEqualTo(USER_ID);
+    }
+
+    // NOTE: should only called by tests that indirectly needs to check user assignments (like
+    // isUserVisible), not by tests for the user assignment methods per se.
+    protected final void assignUserToDisplay(@UserIdInt int userId, int displayId) {
+        mUsersOnSecondaryDisplays.put(userId, displayId);
+    }
+
+    protected final void assertNoUserAssignedToDisplay() {
+        assertWithMessage("mUsersOnSecondaryDisplays()").that(usersOnSecondaryDisplaysAsMap())
+                .isEmpty();
+    }
+
+    protected final void assertUserAssignedToDisplay(@UserIdInt int userId, int displayId) {
+        assertWithMessage("mUsersOnSecondaryDisplays()").that(usersOnSecondaryDisplaysAsMap())
+                .containsExactly(userId, displayId);
+    }
+
+    private Map<Integer, Integer> usersOnSecondaryDisplaysAsMap() {
+        int size = mUsersOnSecondaryDisplays.size();
+        Map<Integer, Integer> map = new LinkedHashMap<>(size);
+        for (int i = 0; i < size; i++) {
+            map.put(mUsersOnSecondaryDisplays.keyAt(i), mUsersOnSecondaryDisplays.valueAt(i));
+        }
+        return map;
+    }
+}
diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java
index 2fac31e..d477cb6 100644
--- a/services/tests/mockingservicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/AgentTrendCalculatorTest.java
@@ -73,7 +73,12 @@
         }
 
         @Override
-        long getHardSatiatedConsumptionLimit() {
+        long getMinSatiatedConsumptionLimit() {
+            return 0;
+        }
+
+        @Override
+        long getMaxSatiatedConsumptionLimit() {
             return 0;
         }
 
diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/AlarmManagerEconomicPolicyTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/AlarmManagerEconomicPolicyTest.java
index fb3e8f2..84a61c7 100644
--- a/services/tests/mockingservicestests/src/com/android/server/tare/AlarmManagerEconomicPolicyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/AlarmManagerEconomicPolicyTest.java
@@ -132,8 +132,10 @@
     public void testDefaults() {
         assertEquals(EconomyManager.DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT_CAKES,
                 mEconomicPolicy.getInitialSatiatedConsumptionLimit());
-        assertEquals(EconomyManager.DEFAULT_AM_HARD_CONSUMPTION_LIMIT_CAKES,
-                mEconomicPolicy.getHardSatiatedConsumptionLimit());
+        assertEquals(EconomyManager.DEFAULT_AM_MIN_CONSUMPTION_LIMIT_CAKES,
+                mEconomicPolicy.getMinSatiatedConsumptionLimit());
+        assertEquals(EconomyManager.DEFAULT_AM_MAX_CONSUMPTION_LIMIT_CAKES,
+                mEconomicPolicy.getMaxSatiatedConsumptionLimit());
         final String pkgRestricted = "com.pkg.restricted";
         when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true);
         assertEquals(0, mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
@@ -150,13 +152,15 @@
     @Test
     public void testConstantsUpdating_ValidValues() {
         setDeviceConfigCakes(EconomyManager.KEY_AM_INITIAL_CONSUMPTION_LIMIT, arcToCake(5));
-        setDeviceConfigCakes(EconomyManager.KEY_AM_HARD_CONSUMPTION_LIMIT, arcToCake(25));
+        setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_CONSUMPTION_LIMIT, arcToCake(3));
+        setDeviceConfigCakes(EconomyManager.KEY_AM_MAX_CONSUMPTION_LIMIT, arcToCake(25));
         setDeviceConfigCakes(EconomyManager.KEY_AM_MAX_SATIATED_BALANCE, arcToCake(10));
         setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(9));
         setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(7));
 
         assertEquals(arcToCake(5), mEconomicPolicy.getInitialSatiatedConsumptionLimit());
-        assertEquals(arcToCake(25), mEconomicPolicy.getHardSatiatedConsumptionLimit());
+        assertEquals(arcToCake(3), mEconomicPolicy.getMinSatiatedConsumptionLimit());
+        assertEquals(arcToCake(25), mEconomicPolicy.getMaxSatiatedConsumptionLimit());
         final String pkgRestricted = "com.pkg.restricted";
         when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true);
         assertEquals(arcToCake(0), mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
@@ -171,13 +175,15 @@
     public void testConstantsUpdating_InvalidValues() {
         // Test negatives.
         setDeviceConfigCakes(EconomyManager.KEY_AM_INITIAL_CONSUMPTION_LIMIT, arcToCake(-5));
-        setDeviceConfigCakes(EconomyManager.KEY_AM_HARD_CONSUMPTION_LIMIT, arcToCake(-5));
+        setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_CONSUMPTION_LIMIT, arcToCake(-5));
+        setDeviceConfigCakes(EconomyManager.KEY_AM_MAX_CONSUMPTION_LIMIT, arcToCake(-5));
         setDeviceConfigCakes(EconomyManager.KEY_AM_MAX_SATIATED_BALANCE, arcToCake(-1));
         setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(-2));
         setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(-3));
 
         assertEquals(arcToCake(1), mEconomicPolicy.getInitialSatiatedConsumptionLimit());
-        assertEquals(arcToCake(1), mEconomicPolicy.getHardSatiatedConsumptionLimit());
+        assertEquals(arcToCake(1), mEconomicPolicy.getMinSatiatedConsumptionLimit());
+        assertEquals(arcToCake(1), mEconomicPolicy.getMaxSatiatedConsumptionLimit());
         final String pkgRestricted = "com.pkg.restricted";
         when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true);
         assertEquals(arcToCake(0), mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
@@ -188,14 +194,16 @@
         assertEquals(arcToCake(0), mEconomicPolicy.getMinSatiatedBalance(0, "com.any.other.app"));
 
         // Test min+max reversed.
-        setDeviceConfigCakes(EconomyManager.KEY_AM_INITIAL_CONSUMPTION_LIMIT, arcToCake(5));
-        setDeviceConfigCakes(EconomyManager.KEY_AM_HARD_CONSUMPTION_LIMIT, arcToCake(3));
+        setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_CONSUMPTION_LIMIT, arcToCake(5));
+        setDeviceConfigCakes(EconomyManager.KEY_AM_INITIAL_CONSUMPTION_LIMIT, arcToCake(4));
+        setDeviceConfigCakes(EconomyManager.KEY_AM_MAX_CONSUMPTION_LIMIT, arcToCake(3));
         setDeviceConfigCakes(EconomyManager.KEY_AM_MAX_SATIATED_BALANCE, arcToCake(10));
         setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(11));
         setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(13));
 
         assertEquals(arcToCake(5), mEconomicPolicy.getInitialSatiatedConsumptionLimit());
-        assertEquals(arcToCake(5), mEconomicPolicy.getHardSatiatedConsumptionLimit());
+        assertEquals(arcToCake(5), mEconomicPolicy.getMinSatiatedConsumptionLimit());
+        assertEquals(arcToCake(5), mEconomicPolicy.getMaxSatiatedConsumptionLimit());
         assertEquals(arcToCake(0), mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
         assertEquals(arcToCake(13), mEconomicPolicy.getMaxSatiatedBalance(0, "com.any.other.app"));
         assertEquals(arcToCake(13), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/CompleteEconomicPolicyTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/CompleteEconomicPolicyTest.java
index 6da4ab7..cad608f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/tare/CompleteEconomicPolicyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/CompleteEconomicPolicyTest.java
@@ -155,9 +155,12 @@
         assertEquals(EconomyManager.DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT_CAKES
                 + EconomyManager.DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT_CAKES,
                 mEconomicPolicy.getInitialSatiatedConsumptionLimit());
-        assertEquals(EconomyManager.DEFAULT_JS_HARD_CONSUMPTION_LIMIT_CAKES
-                + EconomyManager.DEFAULT_AM_HARD_CONSUMPTION_LIMIT_CAKES,
-                mEconomicPolicy.getHardSatiatedConsumptionLimit());
+        assertEquals(EconomyManager.DEFAULT_JS_MIN_CONSUMPTION_LIMIT_CAKES
+                + EconomyManager.DEFAULT_AM_MIN_CONSUMPTION_LIMIT_CAKES,
+                mEconomicPolicy.getMinSatiatedConsumptionLimit());
+        assertEquals(EconomyManager.DEFAULT_JS_MAX_CONSUMPTION_LIMIT_CAKES
+                + EconomyManager.DEFAULT_AM_MAX_CONSUMPTION_LIMIT_CAKES,
+                mEconomicPolicy.getMaxSatiatedConsumptionLimit());
         final String pkgRestricted = "com.pkg.restricted";
         when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true);
         assertEquals(0, mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
@@ -178,8 +181,10 @@
     public void testConstantsUpdated() {
         setDeviceConfigCakes(EconomyManager.KEY_JS_INITIAL_CONSUMPTION_LIMIT, arcToCake(4));
         setDeviceConfigCakes(EconomyManager.KEY_AM_INITIAL_CONSUMPTION_LIMIT, arcToCake(6));
-        setDeviceConfigCakes(EconomyManager.KEY_JS_HARD_CONSUMPTION_LIMIT, arcToCake(24));
-        setDeviceConfigCakes(EconomyManager.KEY_AM_HARD_CONSUMPTION_LIMIT, arcToCake(26));
+        setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_CONSUMPTION_LIMIT, arcToCake(2));
+        setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_CONSUMPTION_LIMIT, arcToCake(3));
+        setDeviceConfigCakes(EconomyManager.KEY_JS_MAX_CONSUMPTION_LIMIT, arcToCake(24));
+        setDeviceConfigCakes(EconomyManager.KEY_AM_MAX_CONSUMPTION_LIMIT, arcToCake(26));
         setDeviceConfigCakes(EconomyManager.KEY_JS_MAX_SATIATED_BALANCE, arcToCake(9));
         setDeviceConfigCakes(EconomyManager.KEY_AM_MAX_SATIATED_BALANCE, arcToCake(11));
         setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(8));
@@ -188,7 +193,8 @@
         setDeviceConfigCakes(EconomyManager.KEY_AM_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(2));
 
         assertEquals(arcToCake(10), mEconomicPolicy.getInitialSatiatedConsumptionLimit());
-        assertEquals(arcToCake(50), mEconomicPolicy.getHardSatiatedConsumptionLimit());
+        assertEquals(arcToCake(5), mEconomicPolicy.getMinSatiatedConsumptionLimit());
+        assertEquals(arcToCake(50), mEconomicPolicy.getMaxSatiatedConsumptionLimit());
         final String pkgRestricted = "com.pkg.restricted";
         when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true);
         assertEquals(arcToCake(0), mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
@@ -206,8 +212,10 @@
         setDeviceConfigBoolean(EconomyManager.KEY_ENABLE_POLICY_JOB_SCHEDULER, false);
         assertEquals(EconomyManager.DEFAULT_AM_INITIAL_CONSUMPTION_LIMIT_CAKES,
                 mEconomicPolicy.getInitialSatiatedConsumptionLimit());
-        assertEquals(EconomyManager.DEFAULT_AM_HARD_CONSUMPTION_LIMIT_CAKES,
-                mEconomicPolicy.getHardSatiatedConsumptionLimit());
+        assertEquals(EconomyManager.DEFAULT_AM_MIN_CONSUMPTION_LIMIT_CAKES,
+                mEconomicPolicy.getMinSatiatedConsumptionLimit());
+        assertEquals(EconomyManager.DEFAULT_AM_MAX_CONSUMPTION_LIMIT_CAKES,
+                mEconomicPolicy.getMaxSatiatedConsumptionLimit());
         final String pkgRestricted = "com.pkg.restricted";
         when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true);
         assertEquals(0, mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
@@ -229,8 +237,10 @@
         setDeviceConfigBoolean(EconomyManager.KEY_ENABLE_POLICY_JOB_SCHEDULER, true);
         assertEquals(EconomyManager.DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT_CAKES,
                 mEconomicPolicy.getInitialSatiatedConsumptionLimit());
-        assertEquals(EconomyManager.DEFAULT_JS_HARD_CONSUMPTION_LIMIT_CAKES,
-                mEconomicPolicy.getHardSatiatedConsumptionLimit());
+        assertEquals(EconomyManager.DEFAULT_JS_MIN_CONSUMPTION_LIMIT_CAKES,
+                mEconomicPolicy.getMinSatiatedConsumptionLimit());
+        assertEquals(EconomyManager.DEFAULT_JS_MAX_CONSUMPTION_LIMIT_CAKES,
+                mEconomicPolicy.getMaxSatiatedConsumptionLimit());
         when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true);
         assertEquals(0, mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
         assertEquals(EconomyManager.DEFAULT_JS_MAX_SATIATED_BALANCE_CAKES,
diff --git a/services/tests/mockingservicestests/src/com/android/server/tare/JobSchedulerEconomicPolicyTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/JobSchedulerEconomicPolicyTest.java
index b7bbcd75..ebf760c 100644
--- a/services/tests/mockingservicestests/src/com/android/server/tare/JobSchedulerEconomicPolicyTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/JobSchedulerEconomicPolicyTest.java
@@ -132,8 +132,10 @@
     public void testDefaults() {
         assertEquals(EconomyManager.DEFAULT_JS_INITIAL_CONSUMPTION_LIMIT_CAKES,
                 mEconomicPolicy.getInitialSatiatedConsumptionLimit());
-        assertEquals(EconomyManager.DEFAULT_JS_HARD_CONSUMPTION_LIMIT_CAKES,
-                mEconomicPolicy.getHardSatiatedConsumptionLimit());
+        assertEquals(EconomyManager.DEFAULT_JS_MIN_CONSUMPTION_LIMIT_CAKES,
+                mEconomicPolicy.getMinSatiatedConsumptionLimit());
+        assertEquals(EconomyManager.DEFAULT_JS_MAX_CONSUMPTION_LIMIT_CAKES,
+                mEconomicPolicy.getMaxSatiatedConsumptionLimit());
 
         final String pkgRestricted = "com.pkg.restricted";
         when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true);
@@ -171,7 +173,8 @@
     @Test
     public void testConstantsUpdating_ValidValues() {
         setDeviceConfigCakes(EconomyManager.KEY_JS_INITIAL_CONSUMPTION_LIMIT, arcToCake(5));
-        setDeviceConfigCakes(EconomyManager.KEY_JS_HARD_CONSUMPTION_LIMIT, arcToCake(25));
+        setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_CONSUMPTION_LIMIT, arcToCake(2));
+        setDeviceConfigCakes(EconomyManager.KEY_JS_MAX_CONSUMPTION_LIMIT, arcToCake(25));
         setDeviceConfigCakes(EconomyManager.KEY_JS_MAX_SATIATED_BALANCE, arcToCake(10));
         setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(6));
         setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(4));
@@ -179,7 +182,8 @@
                 arcToCake(1));
 
         assertEquals(arcToCake(5), mEconomicPolicy.getInitialSatiatedConsumptionLimit());
-        assertEquals(arcToCake(25), mEconomicPolicy.getHardSatiatedConsumptionLimit());
+        assertEquals(arcToCake(2), mEconomicPolicy.getMinSatiatedConsumptionLimit());
+        assertEquals(arcToCake(25), mEconomicPolicy.getMaxSatiatedConsumptionLimit());
         final String pkgRestricted = "com.pkg.restricted";
         when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true);
         assertEquals(arcToCake(0), mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
@@ -198,7 +202,8 @@
     public void testConstantsUpdating_InvalidValues() {
         // Test negatives.
         setDeviceConfigCakes(EconomyManager.KEY_JS_INITIAL_CONSUMPTION_LIMIT, arcToCake(-5));
-        setDeviceConfigCakes(EconomyManager.KEY_JS_HARD_CONSUMPTION_LIMIT, arcToCake(-5));
+        setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_CONSUMPTION_LIMIT, arcToCake(-5));
+        setDeviceConfigCakes(EconomyManager.KEY_JS_MAX_CONSUMPTION_LIMIT, arcToCake(-5));
         setDeviceConfigCakes(EconomyManager.KEY_JS_MAX_SATIATED_BALANCE, arcToCake(-1));
         setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(-2));
         setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(-3));
@@ -206,7 +211,8 @@
                 arcToCake(-4));
 
         assertEquals(arcToCake(1), mEconomicPolicy.getInitialSatiatedConsumptionLimit());
-        assertEquals(arcToCake(1), mEconomicPolicy.getHardSatiatedConsumptionLimit());
+        assertEquals(arcToCake(1), mEconomicPolicy.getMinSatiatedConsumptionLimit());
+        assertEquals(arcToCake(1), mEconomicPolicy.getMaxSatiatedConsumptionLimit());
         final String pkgRestricted = "com.pkg.restricted";
         when(mIrs.isPackageRestricted(anyInt(), eq(pkgRestricted))).thenReturn(true);
         assertEquals(arcToCake(0), mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
@@ -221,14 +227,16 @@
                 mEconomicPolicy.getMinSatiatedBalance(0, pkgUpdater));
 
         // Test min+max reversed.
-        setDeviceConfigCakes(EconomyManager.KEY_JS_INITIAL_CONSUMPTION_LIMIT, arcToCake(5));
-        setDeviceConfigCakes(EconomyManager.KEY_JS_HARD_CONSUMPTION_LIMIT, arcToCake(3));
+        setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_CONSUMPTION_LIMIT, arcToCake(5));
+        setDeviceConfigCakes(EconomyManager.KEY_JS_INITIAL_CONSUMPTION_LIMIT, arcToCake(4));
+        setDeviceConfigCakes(EconomyManager.KEY_JS_MAX_CONSUMPTION_LIMIT, arcToCake(3));
         setDeviceConfigCakes(EconomyManager.KEY_JS_MAX_SATIATED_BALANCE, arcToCake(10));
         setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_EXEMPTED, arcToCake(11));
         setDeviceConfigCakes(EconomyManager.KEY_JS_MIN_SATIATED_BALANCE_OTHER_APP, arcToCake(13));
 
         assertEquals(arcToCake(5), mEconomicPolicy.getInitialSatiatedConsumptionLimit());
-        assertEquals(arcToCake(5), mEconomicPolicy.getHardSatiatedConsumptionLimit());
+        assertEquals(arcToCake(5), mEconomicPolicy.getMinSatiatedConsumptionLimit());
+        assertEquals(arcToCake(5), mEconomicPolicy.getMaxSatiatedConsumptionLimit());
         assertEquals(arcToCake(0), mEconomicPolicy.getMaxSatiatedBalance(0, pkgRestricted));
         assertEquals(arcToCake(13), mEconomicPolicy.getMaxSatiatedBalance(0, "com.any.other.app"));
         assertEquals(arcToCake(13), mEconomicPolicy.getMinSatiatedBalance(0, pkgExempted));
diff --git a/services/tests/mockingservicestests/src/com/android/server/utils/AlarmQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/utils/AlarmQueueTest.java
index 00d7541..a3a49d70 100644
--- a/services/tests/mockingservicestests/src/com/android/server/utils/AlarmQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/utils/AlarmQueueTest.java
@@ -35,6 +35,7 @@
 
 import android.app.AlarmManager;
 import android.content.Context;
+import android.os.Handler;
 import android.os.Looper;
 import android.os.SystemClock;
 import android.util.ArraySet;
@@ -222,7 +223,8 @@
 
         alarmQueue.addAlarm("com.android.test.1", nowElapsed + HOUR_IN_MILLIS);
         verify(mAlarmManager, timeout(1000).times(1)).setWindow(
-                anyInt(), eq(nowElapsed + HOUR_IN_MILLIS), anyLong(), eq(ALARM_TAG), any(), any());
+                anyInt(), eq(nowElapsed + HOUR_IN_MILLIS), anyLong(), eq(ALARM_TAG), any(), any(
+                        Handler.class));
     }
 
     @Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/utils/quota/CountQuotaTrackerTest.java b/services/tests/mockingservicestests/src/com/android/server/utils/quota/CountQuotaTrackerTest.java
index 608b64e..0d14c9f 100644
--- a/services/tests/mockingservicestests/src/com/android/server/utils/quota/CountQuotaTrackerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/utils/quota/CountQuotaTrackerTest.java
@@ -589,14 +589,14 @@
         // No sessions saved yet.
         mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
         verify(mAlarmManager, never()).setWindow(
-                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
 
         // Test with timing sessions out of window.
         final long now = mInjector.getElapsedRealtime();
         logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - 10 * HOUR_IN_MILLIS, 20);
         mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
         verify(mAlarmManager, never()).setWindow(
-                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
 
         // Test with timing sessions in window but still in quota.
         final long start = now - (6 * HOUR_IN_MILLIS);
@@ -604,25 +604,27 @@
         logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, start, 5);
         mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
         verify(mAlarmManager, never()).setWindow(
-                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
 
         // Add some more sessions, but still in quota.
         logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - 3 * HOUR_IN_MILLIS, 1);
         logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - HOUR_IN_MILLIS, 3);
         mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
         verify(mAlarmManager, never()).setWindow(
-                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
 
         // Test when out of quota.
         logEventsAt(TEST_USER_ID, TEST_PACKAGE, TEST_TAG, now - HOUR_IN_MILLIS, 1);
         mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
         verify(mAlarmManager, timeout(1000).times(1)).setWindow(
-                anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(),
+                any(Handler.class));
 
         // Alarm already scheduled, so make sure it's not scheduled again.
         mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
         verify(mAlarmManager, times(1)).setWindow(
-                anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                anyInt(), eq(expectedAlarmTime), anyLong(), eq(TAG_QUOTA_CHECK), any(),
+                any(Handler.class));
     }
 
     /** Tests that the start alarm is properly rescheduled if the app's category is changed. */
@@ -656,7 +658,8 @@
         mCategorizer.mCategoryToUse = ACTIVE_BUCKET_CATEGORY;
         mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
         inOrder.verify(mAlarmManager, never())
-                .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(),
+                        any(Handler.class));
         inOrder.verify(mAlarmManager, never()).cancel(any(AlarmManager.OnAlarmListener.class));
 
         // And down from there.
@@ -665,41 +668,42 @@
         mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
         inOrder.verify(mAlarmManager, timeout(1000).times(1))
                 .setWindow(anyInt(), eq(expectedWorkingAlarmTime), anyLong(),
-                        eq(TAG_QUOTA_CHECK), any(), any());
+                        eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
 
         final long expectedFrequentAlarmTime = outOfQuotaTime + (8 * HOUR_IN_MILLIS);
         mCategorizer.mCategoryToUse = FREQUENT_BUCKET_CATEGORY;
         mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
         inOrder.verify(mAlarmManager, timeout(1000).times(1))
                 .setWindow(anyInt(), eq(expectedFrequentAlarmTime), anyLong(),
-                        eq(TAG_QUOTA_CHECK), any(), any());
+                        eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
 
         final long expectedRareAlarmTime = outOfQuotaTime + (24 * HOUR_IN_MILLIS);
         mCategorizer.mCategoryToUse = RARE_BUCKET_CATEGORY;
         mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
         inOrder.verify(mAlarmManager, timeout(1000).times(1))
                 .setWindow(anyInt(), eq(expectedRareAlarmTime), anyLong(),
-                        eq(TAG_QUOTA_CHECK), any(), any());
+                        eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
 
         // And back up again.
         mCategorizer.mCategoryToUse = FREQUENT_BUCKET_CATEGORY;
         mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
         inOrder.verify(mAlarmManager, timeout(1000).times(1))
                 .setWindow(anyInt(), eq(expectedFrequentAlarmTime), anyLong(),
-                        eq(TAG_QUOTA_CHECK), any(), any());
+                        eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
 
         mCategorizer.mCategoryToUse = WORKING_SET_BUCKET_CATEGORY;
         mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
         inOrder.verify(mAlarmManager, timeout(1000).times(1))
                 .setWindow(anyInt(), eq(expectedWorkingAlarmTime), anyLong(),
-                        eq(TAG_QUOTA_CHECK), any(), any());
+                        eq(TAG_QUOTA_CHECK), any(), any(Handler.class));
 
         mCategorizer.mCategoryToUse = ACTIVE_BUCKET_CATEGORY;
         mQuotaTracker.maybeScheduleStartAlarmLocked(TEST_USER_ID, TEST_PACKAGE, TEST_TAG);
         inOrder.verify(mAlarmManager, timeout(1000).times(1))
                 .cancel(any(AlarmManager.OnAlarmListener.class));
         inOrder.verify(mAlarmManager, timeout(1000).times(0))
-                .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(), any());
+                .setWindow(anyInt(), anyLong(), anyLong(), eq(TAG_QUOTA_CHECK), any(),
+                        any(Handler.class));
     }
 
     @Test
diff --git a/services/tests/mockingservicestests/utils-mockito/com/android/server/extendedtestutils/ExtendedMockitoUtils.kt b/services/tests/mockingservicestests/utils-mockito/com/android/server/extendedtestutils/ExtendedMockitoUtils.kt
index 72ae77e..a0a67439 100644
--- a/services/tests/mockingservicestests/utils-mockito/com/android/server/extendedtestutils/ExtendedMockitoUtils.kt
+++ b/services/tests/mockingservicestests/utils-mockito/com/android/server/extendedtestutils/ExtendedMockitoUtils.kt
@@ -33,9 +33,14 @@
     override fun thenReturn(value: T) {
         ExtendedMockito.doReturn(value).wheneverStatic(mockedMethod)
     }
+
+    override fun thenDoNothing() {
+        ExtendedMockito.doNothing().wheneverStatic(mockedMethod)
+    }
 }
 
 interface CustomStaticStubber<T> {
     fun thenAnswer(answer: Answer<T>)
     fun thenReturn(value: T)
+    fun thenDoNothing()
 }
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index 8a932f1..61bb57e 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -124,9 +124,6 @@
         ":PackageParserTestApp4",
         ":PackageParserTestApp5",
         ":PackageParserTestApp6",
-        ":apex.test",
-        ":test.rebootless_apex_v1",
-        ":test.rebootless_apex_v2",
         ":com.android.apex.cts.shim.v1_prebuilt",
         ":com.android.apex.cts.shim.v2_different_certificate_prebuilt",
         ":com.android.apex.cts.shim.v2_unsigned_apk_container_prebuilt",
diff --git a/services/tests/servicestests/src/com/android/server/am/AnrHelperTest.java b/services/tests/servicestests/src/com/android/server/am/AnrHelperTest.java
index 0b84a60..e6ab73a 100644
--- a/services/tests/servicestests/src/com/android/server/am/AnrHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/AnrHelperTest.java
@@ -49,6 +49,7 @@
 import java.lang.reflect.Field;
 import java.lang.reflect.Modifier;
 import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutorService;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -62,6 +63,7 @@
     private AnrHelper mAnrHelper;
 
     private ProcessRecord mAnrApp;
+    private ExecutorService mExecutorService;
 
     @Rule
     public ServiceThreadRule mServiceThreadRule = new ServiceThreadRule();
@@ -88,7 +90,9 @@
                         return mServiceThreadRule.getThread().getThreadHandler();
                     }
                 }, mServiceThreadRule.getThread());
-            mAnrHelper = new AnrHelper(service);
+            mExecutorService = mock(ExecutorService.class);
+
+            mAnrHelper = new AnrHelper(service, mExecutorService);
         });
     }
 
@@ -119,7 +123,7 @@
 
         verify(mAnrApp.mErrorState, timeout(TIMEOUT_MS)).appNotResponding(
                 eq(activityShortComponentName), eq(appInfo), eq(parentShortComponentName),
-                eq(parentProcess), eq(aboveSystem), eq(timeoutRecord),
+                eq(parentProcess), eq(aboveSystem), eq(timeoutRecord), eq(mExecutorService),
                 eq(false) /* onlyDumpSelf */);
     }
 
@@ -133,7 +137,7 @@
             processingLatch.await();
             return null;
         }).when(mAnrApp.mErrorState).appNotResponding(anyString(), any(), any(), any(),
-                anyBoolean(), any(), anyBoolean());
+                anyBoolean(), any(), any(), anyBoolean());
         final ApplicationInfo appInfo = new ApplicationInfo();
         final TimeoutRecord timeoutRecord = TimeoutRecord.forInputDispatchWindowUnresponsive(
                 "annotation");
@@ -155,6 +159,7 @@
         processingLatch.countDown();
         // There is only one ANR reported.
         verify(mAnrApp.mErrorState, timeout(TIMEOUT_MS).only()).appNotResponding(
-                anyString(), any(), any(), any(), anyBoolean(), any(), anyBoolean());
+                anyString(), any(), any(), any(), anyBoolean(), any(), eq(mExecutorService),
+                anyBoolean());
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/am/ProcessRecordTests.java b/services/tests/servicestests/src/com/android/server/am/ProcessRecordTests.java
index 70519e4..9cada91 100644
--- a/services/tests/servicestests/src/com/android/server/am/ProcessRecordTests.java
+++ b/services/tests/servicestests/src/com/android/server/am/ProcessRecordTests.java
@@ -45,6 +45,7 @@
 
 import java.lang.reflect.Field;
 import java.lang.reflect.Modifier;
+import java.util.concurrent.ExecutorService;
 
 /**
  * Build/Install/Run:
@@ -58,6 +59,7 @@
 
     private ProcessRecord mProcessRecord;
     private ProcessErrorStateRecord mProcessErrorState;
+    private ExecutorService mExecutorService;
 
     @BeforeClass
     public static void setUpOnce() throws Exception {
@@ -109,6 +111,7 @@
         runWithDexmakerShareClassLoader(() -> {
             mProcessRecord = new ProcessRecord(sService, sContext.getApplicationInfo(),
                     "name", 12345);
+            mExecutorService = mock(ExecutorService.class);
             mProcessErrorState = spy(mProcessRecord.mErrorState);
             doNothing().when(mProcessErrorState).startAppProblemLSP();
             doReturn(false).when(mProcessErrorState).isSilentAnr();
@@ -194,11 +197,11 @@
         assertTrue(mProcessRecord.isKilled());
     }
 
-    private static void appNotResponding(ProcessErrorStateRecord processErrorState,
+    private void appNotResponding(ProcessErrorStateRecord processErrorState,
             String annotation) {
         TimeoutRecord timeoutRecord = TimeoutRecord.forInputDispatchNoFocusedWindow(annotation);
         processErrorState.appNotResponding(null /* activityShortComponentName */, null /* aInfo */,
                 null /* parentShortComponentName */, null /* parentProcess */,
-                false /* aboveSystem */, timeoutRecord, false /* onlyDumpSelf */);
+                false /* aboveSystem */, timeoutRecord, mExecutorService, false /* onlyDumpSelf */);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
index fe92a1d..935d1d8 100644
--- a/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/am/UserControllerTest.java
@@ -24,7 +24,6 @@
 import static android.app.ActivityManagerInternal.ALLOW_NON_FULL_IN_PROFILE;
 import static android.app.ActivityManagerInternal.ALLOW_PROFILES_OR_NON_FULL;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-import static android.os.UserHandle.USER_SYSTEM;
 import static android.testing.DexmakerShareClassLoaderRule.runWithDexmakerShareClassLoader;
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
@@ -403,7 +402,7 @@
         verify(mInjector, times(0)).dismissKeyguard(any(), anyString());
         verify(mInjector.getWindowManager(), times(1)).stopFreezingScreen();
         continueUserSwitchAssertions(TEST_USER_ID, false);
-        verifyOnUserStarting(USER_SYSTEM, /* visible= */ false);
+        verifySystemUserVisibilityChangedNotified(/* visible= */ false);
     }
 
     @Test
@@ -424,7 +423,7 @@
         verify(mInjector, times(1)).dismissKeyguard(any(), anyString());
         verify(mInjector.getWindowManager(), times(1)).stopFreezingScreen();
         continueUserSwitchAssertions(TEST_USER_ID, false);
-        verifyOnUserStarting(USER_SYSTEM, /* visible= */ false);
+        verifySystemUserVisibilityChangedNotified(/* visible= */ false);
     }
 
     @Test
@@ -531,7 +530,7 @@
         assertFalse(mUserController.canStartMoreUsers());
         assertEquals(Arrays.asList(new Integer[] {0, TEST_USER_ID1, TEST_USER_ID2}),
                 mUserController.getRunningUsersLU());
-        verifyOnUserStarting(USER_SYSTEM, /* visible= */ false);
+        verifySystemUserVisibilityChangedNotified(/* visible= */ false);
     }
 
     /**
@@ -964,8 +963,8 @@
         verify(mInjector.getUserManagerInternal(), never()).unassignUserFromDisplay(userId);
     }
 
-    private void verifyOnUserStarting(@UserIdInt int userId, boolean visible) {
-        verify(mInjector).onUserStarting(userId, visible);
+    private void verifySystemUserVisibilityChangedNotified(boolean visible) {
+        verify(mInjector).notifySystemUserVisibilityChanged(visible);
     }
 
     // Should be public to allow mocking
@@ -1108,6 +1107,11 @@
         void onUserStarting(@UserIdInt int userId, boolean visible) {
             Log.i(TAG, "onUserStarting(" + userId + ", " + visible + ")");
         }
+
+        @Override
+        void notifySystemUserVisibilityChanged(boolean visible) {
+            Log.i(TAG, "notifySystemUserVisibilityChanged(" + visible + ")");
+        }
     }
 
     private static class TestHandler extends Handler {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BaseClientMonitorTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BaseClientMonitorTest.java
index 8e6d90c..3a9c0f0 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BaseClientMonitorTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BaseClientMonitorTest.java
@@ -107,7 +107,7 @@
         assertThat(mClientMonitor.getRequestId()).isEqualTo(id);
     }
 
-    private class TestClientMonitor extends BaseClientMonitor implements Interruptable {
+    private class TestClientMonitor extends BaseClientMonitor {
         public boolean mCanceled = false;
 
         TestClientMonitor() {
@@ -129,5 +129,10 @@
         public void cancelWithoutStarting(@NonNull ClientMonitorCallback callback) {
             mCanceled = true;
         }
+
+        @Override
+        public boolean isInterruptable() {
+            return true;
+        }
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java
index 9e9d703..3c77a35 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerOperationTest.java
@@ -57,11 +57,16 @@
 
     public interface FakeHal {}
     public abstract static class InterruptableMonitor<T>
-            extends HalClientMonitor<T> implements  Interruptable {
+            extends HalClientMonitor<T> {
         public InterruptableMonitor() {
             super(null, null, null, null, 0, null, 0, 0,
                     mock(BiometricLogger.class), mock(BiometricContext.class));
         }
+
+        @Override
+        public boolean isInterruptable() {
+            return true;
+        }
     }
 
     @Rule
@@ -293,7 +298,6 @@
 
         assertThat(mOperation.isCanceling()).isTrue();
         verify(mClientMonitor).cancel();
-        verify(mClientMonitor, never()).cancelWithoutStarting(any());
         verify(mClientMonitor, never()).destroy();
 
         mStartedCallbackCaptor.getValue().onClientFinished(mClientMonitor, true);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
index ffacbf3..9f30c75 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/BiometricSchedulerTest.java
@@ -34,7 +34,6 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
-import static org.mockito.Mockito.withSettings;
 
 import android.content.Context;
 import android.hardware.biometrics.BiometricAuthenticator;
@@ -475,8 +474,8 @@
 
     @Test
     public void testInterruptPrecedingClients_whenExpected() {
-        final BaseClientMonitor interruptableMonitor = mock(BaseClientMonitor.class,
-                withSettings().extraInterfaces(Interruptable.class));
+        final BaseClientMonitor interruptableMonitor = mock(BaseClientMonitor.class);
+        when(interruptableMonitor.isInterruptable()).thenReturn(true);
 
         final BaseClientMonitor interrupter = mock(BaseClientMonitor.class);
         when(interrupter.interruptsPrecedingClients()).thenReturn(true);
@@ -491,8 +490,8 @@
 
     @Test
     public void testDoesNotInterruptPrecedingClients_whenNotExpected() {
-        final BaseClientMonitor interruptableMonitor = mock(BaseClientMonitor.class,
-                withSettings().extraInterfaces(Interruptable.class));
+        final BaseClientMonitor interruptableMonitor = mock(BaseClientMonitor.class);
+        when(interruptableMonitor.isInterruptable()).thenReturn(true);
 
         final BaseClientMonitor interrupter = mock(BaseClientMonitor.class);
         when(interrupter.interruptsPrecedingClients()).thenReturn(false);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java
index 5012335..21c9c75 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java
@@ -61,7 +61,7 @@
 
     @Test
     public void noopWhenBothNull() {
-        final SensorOverlays useless = new SensorOverlays(null, null);
+        final SensorOverlays useless = new SensorOverlays(null, null, null);
         useless.show(SENSOR_ID, 2, null);
         useless.hide(SENSOR_ID);
     }
@@ -69,12 +69,12 @@
     @Test
     public void testProvidesUdfps() {
         final List<IUdfpsOverlayController> udfps = new ArrayList<>();
-        SensorOverlays sensorOverlays = new SensorOverlays(null, mSidefpsController);
+        SensorOverlays sensorOverlays = new SensorOverlays(null, mSidefpsController, null);
 
         sensorOverlays.ifUdfps(udfps::add);
         assertThat(udfps).isEmpty();
 
-        sensorOverlays = new SensorOverlays(mUdfpsOverlayController, mSidefpsController);
+        sensorOverlays = new SensorOverlays(mUdfpsOverlayController, mSidefpsController, null);
         sensorOverlays.ifUdfps(udfps::add);
         assertThat(udfps).containsExactly(mUdfpsOverlayController);
     }
@@ -96,7 +96,7 @@
 
     private void testShow(IUdfpsOverlayController udfps, ISidefpsController sidefps)
             throws Exception {
-        final SensorOverlays sensorOverlays = new SensorOverlays(udfps, sidefps);
+        final SensorOverlays sensorOverlays = new SensorOverlays(udfps, sidefps, null);
         final int reason = BiometricOverlayConstants.REASON_UNKNOWN;
         sensorOverlays.show(SENSOR_ID, reason, mAcquisitionClient);
 
@@ -126,7 +126,7 @@
 
     private void testHide(IUdfpsOverlayController udfps, ISidefpsController sidefps)
             throws Exception {
-        final SensorOverlays sensorOverlays = new SensorOverlays(udfps, sidefps);
+        final SensorOverlays sensorOverlays = new SensorOverlays(udfps, sidefps, null);
         sensorOverlays.hide(SENSOR_ID);
 
         if (udfps != null) {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index 1b5db0a..608eeec 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -620,6 +620,20 @@
         verify(mCallback).onClientFinished(any(), eq(true));
     }
 
+    @Test
+    public void sideFpsPowerPressCancelsIsntantly() throws Exception {
+        when(mSensorProps.isAnySidefpsType()).thenReturn(true);
+
+        final FingerprintAuthenticationClient client = createClient(1);
+        client.start(mCallback);
+
+        client.onPowerPressed();
+        mLooper.dispatchAll();
+
+        verify(mCallback, never()).onClientFinished(any(), eq(true));
+        verify(mCallback).onClientFinished(any(), eq(false));
+    }
+
     private FingerprintAuthenticationClient createClient() throws RemoteException {
         return createClient(100 /* version */, true /* allowBackgroundAuthentication */);
     }
@@ -645,7 +659,7 @@
                 9 /* sensorId */, mBiometricLogger, mBiometricContext,
                 true /* isStrongBiometric */,
                 null /* taskStackListener */, mLockoutCache,
-                mUdfpsOverlayController, mSideFpsController, allowBackgroundAuthentication,
+                mUdfpsOverlayController, mSideFpsController, null, allowBackgroundAuthentication,
                 mSensorProps,
                 new Handler(mLooper.getLooper()), 0 /* biometricStrength */, mClock) {
             @Override
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
index 93cbef1..4579fc1 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
@@ -118,6 +118,6 @@
         return new FingerprintDetectClient(mContext, () -> aidl, mToken,
                 6 /* requestId */, mClientMonitorCallbackConverter, 2 /* userId */,
                 "a-test", 1 /* sensorId */, mBiometricLogger, mBiometricContext,
-                mUdfpsOverlayController, true /* isStrongBiometric */);
+                mUdfpsOverlayController, null, true /* isStrongBiometric */);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
index 837b553..38b06c4 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
@@ -28,7 +28,6 @@
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.same;
-import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -66,7 +65,6 @@
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
-import java.util.ArrayList;
 import java.util.function.Consumer;
 
 @Presubmit
@@ -292,6 +290,6 @@
         mClientMonitorCallbackConverter, 0 /* userId */,
         HAT, "owner", mBiometricUtils, 8 /* sensorId */,
         mBiometricLogger, mBiometricContext, mSensorProps, mUdfpsOverlayController,
-        mSideFpsController, 6 /* maxTemplatesPerUser */, FingerprintManager.ENROLL_ENROLL);
+        mSideFpsController, null, 6 /* maxTemplatesPerUser */, FingerprintManager.ENROLL_ENROLL);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index 02bbe65..5fda3d6 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -891,4 +891,34 @@
         verify(mContext).startActivityAsUser(argThat(intent ->
                 intent.filterEquals(blockedAppIntent)), any(), any());
     }
+
+    @Test
+    public void registerRunningAppsChangedListener_onRunningAppsChanged_listenersNotified() {
+        ArraySet<Integer> uids = new ArraySet<>(Arrays.asList(UID_1, UID_2));
+        mDeviceImpl.onVirtualDisplayCreatedLocked(
+                mDeviceImpl.createWindowPolicyController(), DISPLAY_ID);
+        GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+                DISPLAY_ID);
+
+        gwpc.onRunningAppsChanged(uids);
+        mDeviceImpl.onRunningAppsChanged(uids);
+
+        assertThat(gwpc.getRunningAppsChangedListenersSizeForTesting()).isEqualTo(1);
+        verify(mRunningAppsChangedCallback).accept(new ArraySet<>(Arrays.asList(UID_1, UID_2)));
+    }
+
+    @Test
+    public void noRunningAppsChangedListener_onRunningAppsChanged_doesNotThrowException() {
+        ArraySet<Integer> uids = new ArraySet<>(Arrays.asList(UID_1, UID_2));
+        mDeviceImpl.onVirtualDisplayCreatedLocked(
+                mDeviceImpl.createWindowPolicyController(), DISPLAY_ID);
+        GenericWindowPolicyController gwpc = mDeviceImpl.getWindowPolicyControllersForTesting().get(
+                DISPLAY_ID);
+        mDeviceImpl.onVirtualDisplayRemovedLocked(DISPLAY_ID);
+
+        // This call should not throw any exceptions.
+        gwpc.onRunningAppsChanged(uids);
+
+        assertThat(gwpc.getRunningAppsChangedListenersSizeForTesting()).isEqualTo(0);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
index 2b069e3..1fe267f 100644
--- a/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
+++ b/services/tests/servicestests/src/com/android/server/display/DisplayModeDirectorTest.java
@@ -57,7 +57,7 @@
 import android.hardware.display.DisplayManager.DisplayListener;
 import android.hardware.display.DisplayManagerInternal;
 import android.hardware.display.DisplayManagerInternal.RefreshRateLimitation;
-import android.hardware.fingerprint.IUdfpsHbmListener;
+import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
 import android.os.Handler;
 import android.os.IThermalEventListener;
 import android.os.IThermalService;
@@ -1081,10 +1081,10 @@
     public void testUdfpsListenerGetsRegistered() {
         DisplayModeDirector director =
                 createDirectorFromRefreshRateArray(new float[] {60.f, 90.f, 110.f}, 0);
-        verify(mStatusBarMock, never()).setUdfpsHbmListener(any());
+        verify(mStatusBarMock, never()).setUdfpsRefreshRateCallback(any());
 
         director.onBootCompleted();
-        verify(mStatusBarMock).setUdfpsHbmListener(eq(director.getUdpfsObserver()));
+        verify(mStatusBarMock).setUdfpsRefreshRateCallback(eq(director.getUdpfsObserver()));
     }
 
     @Test
@@ -1093,10 +1093,9 @@
                 createDirectorFromRefreshRateArray(new float[] {60.f, 90.f, 110.f}, 0);
         director.start(createMockSensorManager());
         director.onBootCompleted();
-        ArgumentCaptor<IUdfpsHbmListener> captor =
-                ArgumentCaptor.forClass(IUdfpsHbmListener.class);
-        verify(mStatusBarMock).setUdfpsHbmListener(captor.capture());
-        IUdfpsHbmListener hbmListener = captor.getValue();
+        ArgumentCaptor<IUdfpsRefreshRateRequestCallback> captor =
+                ArgumentCaptor.forClass(IUdfpsRefreshRateRequestCallback.class);
+        verify(mStatusBarMock).setUdfpsRefreshRateCallback(captor.capture());
 
         // Should be no vote initially
         Vote vote = director.getVote(DISPLAY_ID, Vote.PRIORITY_UDFPS);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
index 1b867be..8f6bee1 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiControlServiceTest.java
@@ -92,7 +92,6 @@
 
         HdmiCecConfig hdmiCecConfig = new FakeHdmiCecConfig(mContextSpy);
         mLocalDeviceTypes.add(HdmiDeviceInfo.DEVICE_PLAYBACK);
-        mLocalDeviceTypes.add(DEVICE_AUDIO_SYSTEM);
 
         mHdmiControlServiceSpy = spy(new HdmiControlService(mContextSpy, mLocalDeviceTypes,
                 new FakeAudioDeviceVolumeManagerWrapper()));
@@ -480,6 +479,7 @@
                 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
                 HdmiControlManager.HDMI_CEC_VERSION_2_0);
         mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
         mTestLooper.dispatchAll();
 
         mNativeWrapper.onCecMessage(HdmiCecMessageBuilder.buildGiveFeatures(Constants.ADDR_TV,
@@ -501,6 +501,7 @@
                 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
                 HdmiControlManager.HDMI_CEC_VERSION_1_4_B);
         mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
         mTestLooper.dispatchAll();
 
         HdmiCecMessage reportFeatures = ReportFeaturesMessage.build(
@@ -517,6 +518,7 @@
                 HdmiControlManager.CEC_SETTING_NAME_HDMI_CEC_VERSION,
                 HdmiControlManager.HDMI_CEC_VERSION_2_0);
         mHdmiControlServiceSpy.setControlEnabled(HdmiControlManager.HDMI_CEC_CONTROL_ENABLED);
+        mHdmiControlServiceSpy.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
         mTestLooper.dispatchAll();
 
         HdmiCecMessage reportFeatures = ReportFeaturesMessage.build(
diff --git a/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt b/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
index c68db34..6590a2b 100644
--- a/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
+++ b/services/tests/servicestests/src/com/android/server/input/BatteryControllerTests.kt
@@ -36,6 +36,7 @@
 import com.android.server.input.BatteryController.POLLING_PERIOD_MILLIS
 import com.android.server.input.BatteryController.UEventManager
 import com.android.server.input.BatteryController.UEventManager.UEventBatteryListener
+import com.android.server.input.BatteryController.USI_BATTERY_VALIDITY_DURATION_MILLIS
 import org.hamcrest.Description
 import org.hamcrest.Matcher
 import org.hamcrest.MatcherAssert.assertThat
@@ -528,10 +529,109 @@
             matchesState(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.64f))
 
         // The battery is no longer present after the timeout expires.
-        testLooper.moveTimeForward(BatteryController.USI_BATTERY_VALIDITY_DURATION_MILLIS - 1)
+        testLooper.moveTimeForward(USI_BATTERY_VALIDITY_DURATION_MILLIS - 1)
         testLooper.dispatchNext()
         listener.verifyNotified(isInvalidBatteryState(USI_DEVICE_ID), times(2))
         assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
             isInvalidBatteryState(USI_DEVICE_ID))
     }
+
+    @Test
+    fun testStylusPresenceExtendsValidUsiBatteryState() {
+        `when`(native.getBatteryDevicePath(USI_DEVICE_ID)).thenReturn("/sys/dev/usi_device")
+        `when`(native.getBatteryStatus(USI_DEVICE_ID)).thenReturn(STATUS_DISCHARGING)
+        `when`(native.getBatteryCapacity(USI_DEVICE_ID)).thenReturn(78)
+
+        addInputDevice(USI_DEVICE_ID, supportsUsi = true)
+        testLooper.dispatchNext()
+        val uEventListener = ArgumentCaptor.forClass(UEventBatteryListener::class.java)
+        verify(uEventManager)
+            .addListener(uEventListener.capture(), eq("DEVPATH=/dev/usi_device"))
+
+        // There is a UEvent signaling a battery change. The battery state is now valid.
+        uEventListener.value!!.onBatteryUEvent(TIMESTAMP)
+        val listener = createMockListener()
+        batteryController.registerBatteryListener(USI_DEVICE_ID, listener, PID)
+        listener.verifyNotified(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.78f)
+        assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
+            matchesState(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.78f))
+
+        // Stylus presence is detected before the validity timeout expires.
+        testLooper.moveTimeForward(100)
+        testLooper.dispatchAll()
+        batteryController.notifyStylusGestureStarted(USI_DEVICE_ID, TIMESTAMP)
+
+        // Ensure that timeout was extended, and the battery state is now valid for longer.
+        testLooper.moveTimeForward(USI_BATTERY_VALIDITY_DURATION_MILLIS - 100)
+        testLooper.dispatchAll()
+        assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
+            matchesState(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.78f))
+
+        // Ensure the validity period expires after the expected amount of time.
+        testLooper.moveTimeForward(100)
+        testLooper.dispatchNext()
+        listener.verifyNotified(isInvalidBatteryState(USI_DEVICE_ID))
+        assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
+            isInvalidBatteryState(USI_DEVICE_ID))
+    }
+
+    @Test
+    fun testStylusPresenceMakesUsiBatteryStateValid() {
+        `when`(native.getBatteryDevicePath(USI_DEVICE_ID)).thenReturn("/sys/dev/usi_device")
+        `when`(native.getBatteryStatus(USI_DEVICE_ID)).thenReturn(STATUS_DISCHARGING)
+        `when`(native.getBatteryCapacity(USI_DEVICE_ID)).thenReturn(78)
+
+        addInputDevice(USI_DEVICE_ID, supportsUsi = true)
+        testLooper.dispatchNext()
+        val uEventListener = ArgumentCaptor.forClass(UEventBatteryListener::class.java)
+        verify(uEventManager)
+            .addListener(uEventListener.capture(), eq("DEVPATH=/dev/usi_device"))
+
+        // The USI battery state is initially invalid.
+        val listener = createMockListener()
+        batteryController.registerBatteryListener(USI_DEVICE_ID, listener, PID)
+        listener.verifyNotified(isInvalidBatteryState(USI_DEVICE_ID))
+        assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
+            isInvalidBatteryState(USI_DEVICE_ID))
+
+        // A stylus presence is detected. This validates the battery state.
+        batteryController.notifyStylusGestureStarted(USI_DEVICE_ID, TIMESTAMP)
+
+        listener.verifyNotified(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.78f)
+        assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
+            matchesState(USI_DEVICE_ID, status = STATUS_DISCHARGING, capacity = 0.78f))
+    }
+
+    @Test
+    fun testStylusPresenceDoesNotMakeUsiBatteryStateValidAtBoot() {
+        `when`(native.getBatteryDevicePath(USI_DEVICE_ID)).thenReturn("/sys/dev/usi_device")
+        // At boot, the USI device always reports a capacity value of 0.
+        `when`(native.getBatteryStatus(USI_DEVICE_ID)).thenReturn(STATUS_UNKNOWN)
+        `when`(native.getBatteryCapacity(USI_DEVICE_ID)).thenReturn(0)
+
+        addInputDevice(USI_DEVICE_ID, supportsUsi = true)
+        testLooper.dispatchNext()
+        val uEventListener = ArgumentCaptor.forClass(UEventBatteryListener::class.java)
+        verify(uEventManager)
+            .addListener(uEventListener.capture(), eq("DEVPATH=/dev/usi_device"))
+
+        // The USI battery state is initially invalid.
+        val listener = createMockListener()
+        batteryController.registerBatteryListener(USI_DEVICE_ID, listener, PID)
+        listener.verifyNotified(isInvalidBatteryState(USI_DEVICE_ID))
+        assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
+            isInvalidBatteryState(USI_DEVICE_ID))
+
+        // Since the capacity reported is 0, stylus presence does not validate the battery state.
+        batteryController.notifyStylusGestureStarted(USI_DEVICE_ID, TIMESTAMP)
+
+        assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
+            isInvalidBatteryState(USI_DEVICE_ID))
+
+        // However, if a UEvent reports a battery capacity of 0, the battery state is now valid.
+        uEventListener.value!!.onBatteryUEvent(TIMESTAMP)
+        listener.verifyNotified(USI_DEVICE_ID, status = STATUS_UNKNOWN, capacity = 0f)
+        assertThat("battery state matches", batteryController.getBatteryState(USI_DEVICE_ID),
+            matchesState(USI_DEVICE_ID, status = STATUS_UNKNOWN, capacity = 0f))
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java b/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java
index a5fedef..21d2784 100644
--- a/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java
+++ b/services/tests/servicestests/src/com/android/server/job/WorkTypeConfigTest.java
@@ -36,7 +36,6 @@
 
 import com.android.server.job.JobConcurrencyManager.WorkTypeConfig;
 
-import org.junit.After;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -59,30 +58,6 @@
     private static final String KEY_MIN_BGUSER_IMPORTANT = "concurrency_min_bguser_important_test";
     private static final String KEY_MIN_BGUSER = "concurrency_min_bguser_test";
 
-    @After
-    public void tearDown() throws Exception {
-        resetConfig();
-    }
-
-    private void resetConfig() {
-        // DeviceConfig.resetToDefaults() doesn't work here. Need to reset constants manually.
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_TOTAL, null, false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_TOP, null, false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_FGS, null, false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_EJ, null, false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_BG, null, false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
-                KEY_MAX_BGUSER_IMPORTANT, null, false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MAX_BGUSER, null, false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_TOP, null, false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_FGS, null, false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_EJ, null, false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_BG, null, false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER,
-                KEY_MIN_BGUSER_IMPORTANT, null, false);
-        DeviceConfig.setProperty(DeviceConfig.NAMESPACE_JOB_SCHEDULER, KEY_MIN_BGUSER, null, false);
-    }
-
     private void check(@Nullable DeviceConfig.Properties config,
             int defaultTotal,
             @NonNull List<Pair<Integer, Integer>> defaultMin,
@@ -90,10 +65,6 @@
             boolean expectedValid, int expectedTotal,
             @NonNull List<Pair<Integer, Integer>> expectedMinLimits,
             @NonNull List<Pair<Integer, Integer>> expectedMaxLimits) throws Exception {
-        resetConfig();
-        if (config != null) {
-            DeviceConfig.setProperties(config);
-        }
 
         final WorkTypeConfig counts;
         try {
@@ -112,7 +83,9 @@
             }
         }
 
-        counts.update(DeviceConfig.getProperties(DeviceConfig.NAMESPACE_JOB_SCHEDULER));
+        if (config != null) {
+            counts.update(config);
+        }
 
         assertEquals(expectedTotal, counts.getMaxTotal());
         for (Pair<Integer, Integer> min : expectedMinLimits) {
diff --git a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
index dbcd38c..dc9f907 100644
--- a/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/locales/LocaleManagerServiceTest.java
@@ -229,6 +229,29 @@
         }
     }
 
+    @Test(expected = SecurityException.class)
+    public void testGetApplicationLocales_currentImeQueryNonForegroundAppLocales_fails()
+            throws Exception {
+        doReturn(DEFAULT_UID).when(mMockPackageManager)
+                .getPackageUidAsUser(anyString(), any(), anyInt());
+        doReturn(new PackageConfig(/* nightMode = */ 0, DEFAULT_LOCALES))
+                .when(mMockActivityTaskManager).getApplicationConfig(anyString(), anyInt());
+        String imPkgName = getCurrentInputMethodPackageName();
+        doReturn(Binder.getCallingUid()).when(mMockPackageManager)
+                .getPackageUidAsUser(eq(imPkgName), any(), anyInt());
+        doReturn(false).when(mMockActivityManager).isAppForeground(anyInt());
+        setUpFailingPermissionCheckFor(Manifest.permission.READ_APP_SPECIFIC_LOCALES);
+
+        try {
+            mLocaleManagerService.getApplicationLocales(DEFAULT_PACKAGE_NAME, DEFAULT_USER_ID);
+            fail("Expected SecurityException");
+        } finally {
+            verify(mMockContext).enforceCallingOrSelfPermission(
+                    eq(android.Manifest.permission.READ_APP_SPECIFIC_LOCALES),
+                    anyString());
+        }
+    }
+
     @Test
     public void testGetApplicationLocales_appSpecificConfigAbsent_returnsEmptyList()
             throws Exception {
@@ -307,7 +330,7 @@
     }
 
     @Test
-    public void testGetApplicationLocales_callerIsCurrentInputMethod_returnsLocales()
+    public void testGetApplicationLocales_currentImeQueryForegroundAppLocales_returnsLocales()
             throws Exception {
         doReturn(DEFAULT_UID).when(mMockPackageManager)
                 .getPackageUidAsUser(anyString(), any(), anyInt());
@@ -316,6 +339,7 @@
         String imPkgName = getCurrentInputMethodPackageName();
         doReturn(Binder.getCallingUid()).when(mMockPackageManager)
                 .getPackageUidAsUser(eq(imPkgName), any(), anyInt());
+        doReturn(true).when(mMockActivityManager).isAppForeground(anyInt());
 
         LocaleList locales =
                 mLocaleManagerService.getApplicationLocales(
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java
index 3f55f1b..ec61b87 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplRebootTests.java
@@ -20,6 +20,7 @@
 
 import android.content.om.OverlayIdentifier;
 import android.content.om.OverlayInfo;
+import android.content.pm.UserPackage;
 
 import androidx.test.runner.AndroidJUnit4;
 
@@ -54,7 +55,7 @@
         addPackage(target(otherTarget), USER);
         addPackage(overlay(OVERLAY, TARGET), USER);
 
-        final Set<PackageAndUser> allPackages = Set.of(new PackageAndUser(TARGET, USER));
+        final Set<UserPackage> allPackages = Set.of(UserPackage.of(USER, TARGET));
 
         // The result should be the same for every time
         assertThat(impl.updateOverlaysForUser(USER)).isEqualTo(allPackages);
@@ -67,7 +68,7 @@
         addPackage(target(TARGET), USER);
         addPackage(overlay(OVERLAY, TARGET), USER);
 
-        final Set<PackageAndUser> allPackages = Set.of(new PackageAndUser(TARGET, USER));
+        final Set<UserPackage> allPackages = Set.of(UserPackage.of(USER, TARGET));
 
         configureSystemOverlay(OVERLAY, ConfigState.IMMUTABLE_DISABLED, 0 /* priority */);
         expect.that(impl.updateOverlaysForUser(USER)).isEqualTo(allPackages);
@@ -101,7 +102,7 @@
         addPackage(overlay(OVERLAY, TARGET), USER);
         configureSystemOverlay(OVERLAY, ConfigState.MUTABLE_DISABLED, 0 /* priority */);
 
-        final Set<PackageAndUser> allPackages = Set.of(new PackageAndUser(TARGET, USER));
+        final Set<UserPackage> allPackages = Set.of(UserPackage.of(USER, TARGET));
 
         expect.that(impl.updateOverlaysForUser(USER)).isEqualTo(allPackages);
         final OverlayInfo o1 = impl.getOverlayInfo(IDENTIFIER, USER);
@@ -133,7 +134,7 @@
         addPackage(target(TARGET), USER);
         addPackage(overlay(OVERLAY, TARGET), USER);
 
-        final Set<PackageAndUser> allPackages = Set.of(new PackageAndUser(TARGET, USER));
+        final Set<UserPackage> allPackages = Set.of(UserPackage.of(USER, TARGET));
 
         final Consumer<ConfigState> setOverlay = (state -> {
             configureSystemOverlay(OVERLAY, state, 0 /* priority */);
@@ -185,7 +186,7 @@
         configureSystemOverlay(OVERLAY, ConfigState.MUTABLE_DISABLED, 0 /* priority */);
         configureSystemOverlay(OVERLAY2, ConfigState.MUTABLE_DISABLED, 1 /* priority */);
 
-        final Set<PackageAndUser> allPackages = Set.of(new PackageAndUser(TARGET, USER));
+        final Set<UserPackage> allPackages = Set.of(UserPackage.of(USER, TARGET));
 
         expect.that(impl.updateOverlaysForUser(USER)).isEqualTo(allPackages);
         final OverlayInfo o1 = impl.getOverlayInfo(IDENTIFIER, USER);
@@ -230,7 +231,7 @@
         configureSystemOverlay(OVERLAY, ConfigState.IMMUTABLE_ENABLED, 0 /* priority */);
         configureSystemOverlay(OVERLAY2, ConfigState.IMMUTABLE_ENABLED, 1 /* priority */);
 
-        final Set<PackageAndUser> allPackages = Set.of(new PackageAndUser(TARGET, USER));
+        final Set<UserPackage> allPackages = Set.of(UserPackage.of(USER, TARGET));
 
         expect.that(impl.updateOverlaysForUser(USER)).isEqualTo(allPackages);
         final OverlayInfo o1 = impl.getOverlayInfo(IDENTIFIER, USER);
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java
index f69141d..ab52928 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTests.java
@@ -22,7 +22,6 @@
 import static android.os.OverlayablePolicy.CONFIG_SIGNATURE;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
@@ -30,7 +29,7 @@
 
 import android.content.om.OverlayIdentifier;
 import android.content.om.OverlayInfo;
-import android.util.Pair;
+import android.content.pm.UserPackage;
 
 import androidx.test.runner.AndroidJUnit4;
 
@@ -66,7 +65,7 @@
     @Test
     public void testGetOverlayInfo() throws Exception {
         installAndAssert(overlay(OVERLAY, TARGET), USER,
-                Set.of(new PackageAndUser(OVERLAY, USER), new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, OVERLAY), UserPackage.of(USER, TARGET)));
 
         final OverlayManagerServiceImpl impl = getImpl();
         final OverlayInfo oi = impl.getOverlayInfo(IDENTIFIER, USER);
@@ -79,11 +78,11 @@
     @Test
     public void testGetOverlayInfosForTarget() throws Exception {
         installAndAssert(overlay(OVERLAY, TARGET), USER,
-                Set.of(new PackageAndUser(OVERLAY, USER), new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, OVERLAY), UserPackage.of(USER, TARGET)));
         installAndAssert(overlay(OVERLAY2, TARGET), USER,
-                Set.of(new PackageAndUser(OVERLAY2, USER), new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, OVERLAY2), UserPackage.of(USER, TARGET)));
         installAndAssert(overlay(OVERLAY3, TARGET), USER2,
-                Set.of(new PackageAndUser(OVERLAY3, USER2), new PackageAndUser(TARGET, USER2)));
+                Set.of(UserPackage.of(USER2, OVERLAY3), UserPackage.of(USER2, TARGET)));
 
         final OverlayManagerServiceImpl impl = getImpl();
         final List<OverlayInfo> ois = impl.getOverlayInfosForTarget(TARGET, USER);
@@ -107,13 +106,13 @@
     @Test
     public void testGetOverlayInfosForUser() throws Exception {
         installAndAssert(target(TARGET), USER,
-                Set.of(new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, TARGET)));
         installAndAssert(overlay(OVERLAY, TARGET), USER,
-                Set.of(new PackageAndUser(OVERLAY, USER), new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, OVERLAY), UserPackage.of(USER, TARGET)));
         installAndAssert(overlay(OVERLAY2, TARGET), USER,
-                Set.of(new PackageAndUser(OVERLAY2, USER), new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, OVERLAY2), UserPackage.of(USER, TARGET)));
         installAndAssert(overlay(OVERLAY3, TARGET2), USER,
-                Set.of(new PackageAndUser(OVERLAY3, USER), new PackageAndUser(TARGET2, USER)));
+                Set.of(UserPackage.of(USER, OVERLAY3), UserPackage.of(USER, TARGET2)));
 
         final OverlayManagerServiceImpl impl = getImpl();
         final Map<String, List<OverlayInfo>> everything = impl.getOverlaysForUser(USER);
@@ -138,11 +137,11 @@
     @Test
     public void testPriority() throws Exception {
         installAndAssert(overlay(OVERLAY, TARGET), USER,
-                Set.of(new PackageAndUser(OVERLAY, USER), new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, OVERLAY), UserPackage.of(USER, TARGET)));
         installAndAssert(overlay(OVERLAY2, TARGET), USER,
-                Set.of(new PackageAndUser(OVERLAY2, USER), new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, OVERLAY2), UserPackage.of(USER, TARGET)));
         installAndAssert(overlay(OVERLAY3, TARGET), USER,
-                Set.of(new PackageAndUser(OVERLAY3, USER), new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, OVERLAY3), UserPackage.of(USER, TARGET)));
 
         final OverlayManagerServiceImpl impl = getImpl();
         final OverlayInfo o1 = impl.getOverlayInfo(IDENTIFIER, USER);
@@ -152,15 +151,15 @@
         assertOverlayInfoForTarget(TARGET, USER, o1, o2, o3);
 
         assertEquals(impl.setLowestPriority(IDENTIFIER3, USER),
-                Optional.of(new PackageAndUser(TARGET, USER)));
+                Optional.of(UserPackage.of(USER, TARGET)));
         assertOverlayInfoForTarget(TARGET, USER, o3, o1, o2);
 
         assertEquals(impl.setHighestPriority(IDENTIFIER3, USER),
-                Set.of(new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, TARGET)));
         assertOverlayInfoForTarget(TARGET, USER, o1, o2, o3);
 
         assertEquals(impl.setPriority(IDENTIFIER, IDENTIFIER2, USER),
-                Optional.of(new PackageAndUser(TARGET, USER)));
+                Optional.of(UserPackage.of(USER, TARGET)));
         assertOverlayInfoForTarget(TARGET, USER, o2, o1, o3);
     }
 
@@ -170,47 +169,47 @@
         assertNull(impl.getOverlayInfo(IDENTIFIER, USER));
 
         installAndAssert(overlay(OVERLAY, TARGET), USER,
-                Set.of(new PackageAndUser(OVERLAY, USER), new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, OVERLAY), UserPackage.of(USER, TARGET)));
         assertState(STATE_MISSING_TARGET, IDENTIFIER, USER);
 
         installAndAssert(target(TARGET), USER,
-                Set.of(new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, TARGET)));
         assertState(STATE_DISABLED, IDENTIFIER, USER);
 
         assertEquals(impl.setEnabled(IDENTIFIER, true, USER),
-                Set.of(new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, TARGET)));
         assertState(STATE_ENABLED, IDENTIFIER, USER);
 
         // target upgrades do not change the state of the overlay
         upgradeAndAssert(target(TARGET), USER,
-                Set.of(new PackageAndUser(TARGET, USER)),
-                Set.of(new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, TARGET)),
+                Set.of(UserPackage.of(USER, TARGET)));
         assertState(STATE_ENABLED, IDENTIFIER, USER);
 
         uninstallAndAssert(TARGET, USER,
-                Set.of(new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, TARGET)));
         assertState(STATE_MISSING_TARGET, IDENTIFIER, USER);
 
         installAndAssert(target(TARGET), USER,
-                Set.of(new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, TARGET)));
         assertState(STATE_ENABLED, IDENTIFIER, USER);
     }
 
     @Test
     public void testOnOverlayPackageUpgraded() throws Exception {
         installAndAssert(target(TARGET), USER,
-                Set.of(new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, TARGET)));
         installAndAssert(overlay(OVERLAY, TARGET), USER,
-                Set.of(new PackageAndUser(OVERLAY, USER), new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, OVERLAY), UserPackage.of(USER, TARGET)));
         upgradeAndAssert(overlay(OVERLAY, TARGET), USER,
-                Set.of(new PackageAndUser(TARGET, USER)),
-                Set.of(new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, TARGET)),
+                Set.of(UserPackage.of(USER, TARGET)));
 
         // upgrade to a version where the overlay has changed its target
         upgradeAndAssert(overlay(OVERLAY, TARGET2), USER,
-                Set.of(new PackageAndUser(TARGET, USER)),
-                Set.of(new PackageAndUser(TARGET, USER),
-                        new PackageAndUser(TARGET2, USER)));
+                Set.of(UserPackage.of(USER, TARGET)),
+                Set.of(UserPackage.of(USER, TARGET),
+                        UserPackage.of(USER, TARGET2)));
     }
 
     @Test
@@ -222,10 +221,10 @@
         // request succeeded, and there was a change that needs to be
         // propagated to the rest of the system
         installAndAssert(target(TARGET), USER,
-                Set.of(new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, TARGET)));
         installAndAssert(overlay(OVERLAY, TARGET), USER,
-                Set.of(new PackageAndUser(OVERLAY, USER), new PackageAndUser(TARGET, USER)));
-        assertEquals(Set.of(new PackageAndUser(TARGET, USER)),
+                Set.of(UserPackage.of(USER, OVERLAY), UserPackage.of(USER, TARGET)));
+        assertEquals(Set.of(UserPackage.of(USER, TARGET)),
                 impl.setEnabled(IDENTIFIER, true, USER));
 
         // request succeeded, but nothing changed
@@ -239,9 +238,9 @@
 
         addPackage(target(CONFIG_SIGNATURE_REFERENCE_PKG).setCertificate(CERT_CONFIG_OK), USER);
         installAndAssert(target(TARGET), USER,
-                Set.of(new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, TARGET)));
         installAndAssert(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_OK), USER,
-                Set.of(new PackageAndUser(OVERLAY, USER), new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, OVERLAY), UserPackage.of(USER, TARGET)));
 
         final FakeIdmapDaemon idmapd = getIdmapd();
         final FakeDeviceState state = getState();
@@ -259,9 +258,9 @@
 
         addPackage(target(CONFIG_SIGNATURE_REFERENCE_PKG).setCertificate(CERT_CONFIG_OK), USER);
         installAndAssert(target(TARGET), USER,
-                Set.of(new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, TARGET)));
         installAndAssert(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER,
-                Set.of(new PackageAndUser(OVERLAY, USER), new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, OVERLAY), UserPackage.of(USER, TARGET)));
 
         final FakeIdmapDaemon idmapd = getIdmapd();
         final FakeDeviceState state = getState();
@@ -276,9 +275,9 @@
     public void testConfigSignaturePolicyNoConfig() throws Exception {
         addPackage(target(CONFIG_SIGNATURE_REFERENCE_PKG).setCertificate(CERT_CONFIG_OK), USER);
         installAndAssert(target(TARGET), USER,
-                Set.of(new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, TARGET)));
         installAndAssert(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER,
-                Set.of(new PackageAndUser(OVERLAY, USER), new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, OVERLAY), UserPackage.of(USER, TARGET)));
 
         final FakeIdmapDaemon idmapd = getIdmapd();
         final FakeDeviceState state = getState();
@@ -292,9 +291,9 @@
     @Test
     public void testConfigSignaturePolicyNoRefPkg() throws Exception {
         installAndAssert(target(TARGET), USER,
-                Set.of(new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, TARGET)));
         installAndAssert(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER,
-                Set.of(new PackageAndUser(OVERLAY, USER), new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, OVERLAY), UserPackage.of(USER, TARGET)));
 
         final FakeIdmapDaemon idmapd = getIdmapd();
         final FakeDeviceState state = getState();
@@ -312,9 +311,9 @@
 
         addPackage(app(CONFIG_SIGNATURE_REFERENCE_PKG).setCertificate(CERT_CONFIG_OK), USER);
         installAndAssert(target(TARGET), USER,
-                Set.of(new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, TARGET)));
         installAndAssert(overlay(OVERLAY, TARGET).setCertificate(CERT_CONFIG_NOK), USER,
-                Set.of(new PackageAndUser(OVERLAY, USER), new PackageAndUser(TARGET, USER)));
+                Set.of(UserPackage.of(USER, OVERLAY), UserPackage.of(USER, TARGET)));
 
         final FakeIdmapDaemon idmapd = getIdmapd();
         final FakeDeviceState state = getState();
diff --git a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java
index 301697d..bba7669 100644
--- a/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java
+++ b/services/tests/servicestests/src/com/android/server/om/OverlayManagerServiceImplTestsBase.java
@@ -28,6 +28,7 @@
 import android.content.om.OverlayInfo;
 import android.content.om.OverlayInfo.State;
 import android.content.om.OverlayableInfo;
+import android.content.pm.UserPackage;
 import android.os.FabricatedOverlayInfo;
 import android.os.FabricatedOverlayInternal;
 import android.text.TextUtils;
@@ -164,7 +165,7 @@
      * @throws IllegalStateException if the package is currently installed
      */
     void installAndAssert(@NonNull FakeDeviceState.PackageBuilder pkg, int userId,
-            @NonNull Set<PackageAndUser> onAddedUpdatedPackages)
+            @NonNull Set<UserPackage> onAddedUpdatedPackages)
             throws OperationFailedException {
         if (mState.select(pkg.packageName, userId) != null) {
             throw new IllegalStateException("package " + pkg.packageName + " already installed");
@@ -185,8 +186,8 @@
      * @throws IllegalStateException if the package is not currently installed
      */
     void upgradeAndAssert(FakeDeviceState.PackageBuilder pkg, int userId,
-            @NonNull Set<PackageAndUser> onReplacingUpdatedPackages,
-            @NonNull Set<PackageAndUser> onReplacedUpdatedPackages)
+            @NonNull Set<UserPackage> onReplacingUpdatedPackages,
+            @NonNull Set<UserPackage> onReplacedUpdatedPackages)
             throws OperationFailedException {
         final FakeDeviceState.Package replacedPackage = mState.select(pkg.packageName, userId);
         if (replacedPackage == null) {
@@ -207,7 +208,7 @@
      * @throws IllegalStateException if the package is not currently installed
      */
     void uninstallAndAssert(@NonNull String packageName, int userId,
-            @NonNull Set<PackageAndUser> onRemovedUpdatedPackages) {
+            @NonNull Set<UserPackage> onRemovedUpdatedPackages) {
         final FakeDeviceState.Package pkg = mState.select(packageName, userId);
         if (pkg == null) {
             throw new IllegalStateException("package " + packageName + " not installed");
diff --git a/services/tests/servicestests/src/com/android/server/pm/AppsFilterImplTest.java b/services/tests/servicestests/src/com/android/server/pm/AppsFilterImplTest.java
index c321639..1a8ef9e 100644
--- a/services/tests/servicestests/src/com/android/server/pm/AppsFilterImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/AppsFilterImplTest.java
@@ -387,7 +387,7 @@
 
         // delete user
         when(mSnapshot.getUserInfos()).thenReturn(USER_INFO_LIST);
-        appsFilter.onUserDeleted(ADDED_USER);
+        appsFilter.onUserDeleted(mSnapshot, ADDED_USER);
 
         for (int subjectUserId : USER_ARRAY) {
             for (int otherUserId : USER_ARRAY) {
@@ -925,7 +925,7 @@
         assertTrue(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_OVERLAY_APPID,
                 overlaySetting, actorSetting, SYSTEM_USER));
 
-        appsFilter.removePackage(mSnapshot, targetSetting, false /* isReplace */);
+        appsFilter.removePackage(mSnapshot, targetSetting);
 
         // Actor loses visibility to the overlay via removal of the target
         assertTrue(appsFilter.shouldFilterApplication(mSnapshot, DUMMY_ACTOR_APPID, actorSetting,
@@ -1267,7 +1267,7 @@
         watcher.verifyNoChangeReported("get");
 
         // remove a package
-        appsFilter.removePackage(mSnapshot, seesNothing, false /* isReplace */);
+        appsFilter.removePackage(mSnapshot, seesNothing);
         watcher.verifyChangeReported("removePackage");
     }
 
@@ -1337,7 +1337,7 @@
                 target.getPackageName()));
 
         // New changes don't affect the snapshot
-        appsFilter.removePackage(mSnapshot, target, false);
+        appsFilter.removePackage(mSnapshot, target);
         assertTrue(
                 appsFilter.shouldFilterApplication(mSnapshot, DUMMY_CALLING_APPID, instrumentation,
                         target,
diff --git a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
index fdf9354..0805485 100644
--- a/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/BaseShortcutManagerTest.java
@@ -74,6 +74,7 @@
 import android.content.pm.SigningDetails;
 import android.content.pm.SigningInfo;
 import android.content.pm.UserInfo;
+import android.content.pm.UserPackage;
 import android.content.res.Resources;
 import android.content.res.XmlResourceParser;
 import android.graphics.drawable.Icon;
@@ -97,7 +98,6 @@
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
 import com.android.server.pm.LauncherAppsService.LauncherAppsImpl;
-import com.android.server.pm.ShortcutUser.PackageWithUser;
 import com.android.server.uri.UriGrantsManagerInternal;
 import com.android.server.uri.UriPermissionOwner;
 import com.android.server.wm.ActivityTaskManagerInternal;
@@ -692,9 +692,9 @@
 
     protected Map<String, PackageInfo> mInjectedPackages;
 
-    protected Set<PackageWithUser> mUninstalledPackages;
-    protected Set<PackageWithUser> mDisabledPackages;
-    protected Set<PackageWithUser> mEphemeralPackages;
+    protected Set<UserPackage> mUninstalledPackages;
+    protected Set<UserPackage> mDisabledPackages;
+    protected Set<UserPackage> mEphemeralPackages;
     protected Set<String> mSystemPackages;
 
     protected PackageManager mMockPackageManager;
@@ -1200,28 +1200,28 @@
         if (ENABLE_DUMP) {
             Log.v(TAG, "Uninstall package " + packageName + " / " + userId);
         }
-        mUninstalledPackages.add(PackageWithUser.of(userId, packageName));
+        mUninstalledPackages.add(UserPackage.of(userId, packageName));
     }
 
     protected void installPackage(int userId, String packageName) {
         if (ENABLE_DUMP) {
             Log.v(TAG, "Install package " + packageName + " / " + userId);
         }
-        mUninstalledPackages.remove(PackageWithUser.of(userId, packageName));
+        mUninstalledPackages.remove(UserPackage.of(userId, packageName));
     }
 
     protected void disablePackage(int userId, String packageName) {
         if (ENABLE_DUMP) {
             Log.v(TAG, "Disable package " + packageName + " / " + userId);
         }
-        mDisabledPackages.add(PackageWithUser.of(userId, packageName));
+        mDisabledPackages.add(UserPackage.of(userId, packageName));
     }
 
     protected void enablePackage(int userId, String packageName) {
         if (ENABLE_DUMP) {
             Log.v(TAG, "Enable package " + packageName + " / " + userId);
         }
-        mDisabledPackages.remove(PackageWithUser.of(userId, packageName));
+        mDisabledPackages.remove(UserPackage.of(userId, packageName));
     }
 
     PackageInfo getInjectedPackageInfo(String packageName, @UserIdInt int userId,
@@ -1239,17 +1239,17 @@
         ret.applicationInfo.uid = UserHandle.getUid(userId, pi.applicationInfo.uid);
         ret.applicationInfo.packageName = pi.packageName;
 
-        if (mUninstalledPackages.contains(PackageWithUser.of(userId, packageName))) {
+        if (mUninstalledPackages.contains(UserPackage.of(userId, packageName))) {
             ret.applicationInfo.flags &= ~ApplicationInfo.FLAG_INSTALLED;
         }
-        if (mEphemeralPackages.contains(PackageWithUser.of(userId, packageName))) {
+        if (mEphemeralPackages.contains(UserPackage.of(userId, packageName))) {
             ret.applicationInfo.privateFlags |= ApplicationInfo.PRIVATE_FLAG_INSTANT;
         }
         if (mSystemPackages.contains(packageName)) {
             ret.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
         }
         ret.applicationInfo.enabled =
-                !mDisabledPackages.contains(PackageWithUser.of(userId, packageName));
+                !mDisabledPackages.contains(UserPackage.of(userId, packageName));
 
         if (getSignatures) {
             ret.signatures = null;
diff --git a/services/tests/servicestests/src/com/android/server/pm/CompatibilityModeTest.java b/services/tests/servicestests/src/com/android/server/pm/CompatibilityModeTest.java
index eaa0e9b..f0d389b 100644
--- a/services/tests/servicestests/src/com/android/server/pm/CompatibilityModeTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/CompatibilityModeTest.java
@@ -34,6 +34,7 @@
 
 import com.android.server.pm.parsing.PackageInfoUtils;
 import com.android.server.pm.parsing.pkg.PackageImpl;
+import com.android.server.pm.pkg.PackageStateUnserialized;
 import com.android.server.pm.pkg.PackageUserStateImpl;
 import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 
@@ -46,12 +47,16 @@
 
     private boolean mCompatibilityModeEnabled;;
     private PackageImpl mMockAndroidPackage;
+    private PackageSetting mMockPackageState;
     private PackageUserStateImpl mMockUserState;
 
     @Before
     public void setUp() {
         mCompatibilityModeEnabled = ParsingPackageUtils.sCompatibilityModeEnabled;
         mMockAndroidPackage = mock(PackageImpl.class);
+        mMockPackageState = mock(PackageSetting.class);
+        when(mMockPackageState.getTransientState())
+                .thenReturn(new PackageStateUnserialized(mMockPackageState));
         mMockUserState = new PackageUserStateImpl();
         mMockUserState.setInstalled(true);
     }
@@ -221,7 +226,7 @@
         info.flags |= flags;
         when(mMockAndroidPackage.toAppInfoWithoutState()).thenReturn(info);
         return PackageInfoUtils.generateApplicationInfo(mMockAndroidPackage,
-                0 /*flags*/, mMockUserState, 0 /*userId*/, null);
+                0 /*flags*/, mMockUserState, 0 /*userId*/, mMockPackageState);
     }
 
     private void setGlobalCompatibilityMode(boolean enabled) {
diff --git a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
index 9ce99d6..59f27ec 100644
--- a/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/PackageParserTest.java
@@ -676,27 +676,32 @@
         final File testFile = extractFile(TEST_APP4_APK);
         try {
             final ParsedPackage pkg = new TestPackageParser2().parsePackage(testFile, 0, false);
+            var pkgSetting = mockPkgSetting(pkg);
             ApplicationInfo appInfo = PackageInfoUtils.generateApplicationInfo(pkg, 0,
-                    PackageUserStateInternal.DEFAULT, 0, null);
+                    PackageUserStateInternal.DEFAULT, 0, pkgSetting);
             for (ParsedActivity activity : pkg.getActivities()) {
                 assertNotNull(activity.getMetaData());
                 assertNull(PackageInfoUtils.generateActivityInfo(pkg, activity, 0,
-                        PackageUserStateInternal.DEFAULT, appInfo, 0, null).metaData);
+                        PackageUserStateInternal.DEFAULT, appInfo, 0, pkgSetting)
+                        .metaData);
             }
             for (ParsedProvider provider : pkg.getProviders()) {
                 assertNotNull(provider.getMetaData());
                 assertNull(PackageInfoUtils.generateProviderInfo(pkg, provider, 0,
-                        PackageUserStateInternal.DEFAULT, appInfo, 0, null).metaData);
+                        PackageUserStateInternal.DEFAULT, appInfo, 0, pkgSetting)
+                        .metaData);
             }
             for (ParsedActivity receiver : pkg.getReceivers()) {
                 assertNotNull(receiver.getMetaData());
                 assertNull(PackageInfoUtils.generateActivityInfo(pkg, receiver, 0,
-                        PackageUserStateInternal.DEFAULT, appInfo, 0, null).metaData);
+                        PackageUserStateInternal.DEFAULT, appInfo, 0, pkgSetting)
+                        .metaData);
             }
             for (ParsedService service : pkg.getServices()) {
                 assertNotNull(service.getMetaData());
                 assertNull(PackageInfoUtils.generateServiceInfo(pkg, service, 0,
-                        PackageUserStateInternal.DEFAULT, appInfo, 0, null).metaData);
+                        PackageUserStateInternal.DEFAULT, appInfo, 0, pkgSetting)
+                        .metaData);
             }
         } finally {
             testFile.delete();
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
index 96c0d0a..b20c63c 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest1.java
@@ -83,6 +83,7 @@
 import android.content.pm.PackageInfo;
 import android.content.pm.ShortcutInfo;
 import android.content.pm.ShortcutManager;
+import android.content.pm.UserPackage;
 import android.graphics.Bitmap;
 import android.graphics.Bitmap.CompressFormat;
 import android.graphics.BitmapFactory;
@@ -107,7 +108,6 @@
 import com.android.modules.utils.TypedXmlSerializer;
 import com.android.server.pm.ShortcutService.ConfigConstants;
 import com.android.server.pm.ShortcutService.FileOutputStreamWithPath;
-import com.android.server.pm.ShortcutUser.PackageWithUser;
 
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mockito;
@@ -4081,12 +4081,12 @@
         assertEquals(set(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
                 hashSet(user10.getAllPackagesForTest().keySet()));
         assertEquals(
-                set(PackageWithUser.of(USER_0, LAUNCHER_1),
-                        PackageWithUser.of(USER_0, LAUNCHER_2)),
+                set(UserPackage.of(USER_0, LAUNCHER_1),
+                        UserPackage.of(USER_0, LAUNCHER_2)),
                 hashSet(user0.getAllLaunchersForTest().keySet()));
         assertEquals(
-                set(PackageWithUser.of(USER_10, LAUNCHER_1),
-                        PackageWithUser.of(USER_10, LAUNCHER_2)),
+                set(UserPackage.of(USER_10, LAUNCHER_1),
+                        UserPackage.of(USER_10, LAUNCHER_2)),
                 hashSet(user10.getAllLaunchersForTest().keySet()));
         assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0),
                 "s0_1", "s0_2");
@@ -4113,12 +4113,12 @@
         assertEquals(set(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
                 hashSet(user10.getAllPackagesForTest().keySet()));
         assertEquals(
-                set(PackageWithUser.of(USER_0, LAUNCHER_1),
-                        PackageWithUser.of(USER_0, LAUNCHER_2)),
+                set(UserPackage.of(USER_0, LAUNCHER_1),
+                        UserPackage.of(USER_0, LAUNCHER_2)),
                 hashSet(user0.getAllLaunchersForTest().keySet()));
         assertEquals(
-                set(PackageWithUser.of(USER_10, LAUNCHER_1),
-                        PackageWithUser.of(USER_10, LAUNCHER_2)),
+                set(UserPackage.of(USER_10, LAUNCHER_1),
+                        UserPackage.of(USER_10, LAUNCHER_2)),
                 hashSet(user10.getAllLaunchersForTest().keySet()));
         assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0),
                 "s0_1", "s0_2");
@@ -4145,12 +4145,12 @@
         assertEquals(set(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
                 hashSet(user10.getAllPackagesForTest().keySet()));
         assertEquals(
-                set(PackageWithUser.of(USER_0, LAUNCHER_1),
-                        PackageWithUser.of(USER_0, LAUNCHER_2)),
+                set(UserPackage.of(USER_0, LAUNCHER_1),
+                        UserPackage.of(USER_0, LAUNCHER_2)),
                 hashSet(user0.getAllLaunchersForTest().keySet()));
         assertEquals(
-                set(PackageWithUser.of(USER_10, LAUNCHER_1),
-                        PackageWithUser.of(USER_10, LAUNCHER_2)),
+                set(UserPackage.of(USER_10, LAUNCHER_1),
+                        UserPackage.of(USER_10, LAUNCHER_2)),
                 hashSet(user10.getAllLaunchersForTest().keySet()));
         assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0),
                 "s0_2");
@@ -4176,11 +4176,11 @@
         assertEquals(set(CALLING_PACKAGE_1, CALLING_PACKAGE_2),
                 hashSet(user10.getAllPackagesForTest().keySet()));
         assertEquals(
-                set(PackageWithUser.of(USER_0, LAUNCHER_1),
-                        PackageWithUser.of(USER_0, LAUNCHER_2)),
+                set(UserPackage.of(USER_0, LAUNCHER_1),
+                        UserPackage.of(USER_0, LAUNCHER_2)),
                 hashSet(user0.getAllLaunchersForTest().keySet()));
         assertEquals(
-                set(PackageWithUser.of(USER_10, LAUNCHER_2)),
+                set(UserPackage.of(USER_10, LAUNCHER_2)),
                 hashSet(user10.getAllLaunchersForTest().keySet()));
         assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0),
                 "s0_2");
@@ -4205,11 +4205,11 @@
         assertEquals(set(CALLING_PACKAGE_1),
                 hashSet(user10.getAllPackagesForTest().keySet()));
         assertEquals(
-                set(PackageWithUser.of(USER_0, LAUNCHER_1),
-                        PackageWithUser.of(USER_0, LAUNCHER_2)),
+                set(UserPackage.of(USER_0, LAUNCHER_1),
+                        UserPackage.of(USER_0, LAUNCHER_2)),
                 hashSet(user0.getAllLaunchersForTest().keySet()));
         assertEquals(
-                set(PackageWithUser.of(USER_10, LAUNCHER_2)),
+                set(UserPackage.of(USER_10, LAUNCHER_2)),
                 hashSet(user10.getAllLaunchersForTest().keySet()));
         assertShortcutIds(getLauncherPinnedShortcuts(LAUNCHER_1, USER_0),
                 "s0_2");
@@ -4234,8 +4234,8 @@
         assertEquals(set(CALLING_PACKAGE_1),
                 hashSet(user10.getAllPackagesForTest().keySet()));
         assertEquals(
-                set(PackageWithUser.of(USER_0, LAUNCHER_1),
-                        PackageWithUser.of(USER_0, LAUNCHER_2)),
+                set(UserPackage.of(USER_0, LAUNCHER_1),
+                        UserPackage.of(USER_0, LAUNCHER_2)),
                 hashSet(user0.getAllLaunchersForTest().keySet()));
         assertEquals(
                 set(),
@@ -4263,8 +4263,8 @@
         assertEquals(set(),
                 hashSet(user10.getAllPackagesForTest().keySet()));
         assertEquals(
-                set(PackageWithUser.of(USER_0, LAUNCHER_1),
-                        PackageWithUser.of(USER_0, LAUNCHER_2)),
+                set(UserPackage.of(USER_0, LAUNCHER_1),
+                        UserPackage.of(USER_0, LAUNCHER_2)),
                 hashSet(user0.getAllLaunchersForTest().keySet()));
         assertEquals(set(),
                 hashSet(user10.getAllLaunchersForTest().keySet()));
@@ -5584,12 +5584,12 @@
 
         assertExistsAndShadow(user0.getAllPackagesForTest().get(CALLING_PACKAGE_3));
         assertExistsAndShadow(user0.getAllLaunchersForTest().get(
-                PackageWithUser.of(USER_0, LAUNCHER_1)));
+                UserPackage.of(USER_0, LAUNCHER_1)));
         assertExistsAndShadow(user0.getAllLaunchersForTest().get(
-                PackageWithUser.of(USER_0, LAUNCHER_2)));
+                UserPackage.of(USER_0, LAUNCHER_2)));
 
-        assertNull(user0.getAllLaunchersForTest().get(PackageWithUser.of(USER_0, LAUNCHER_3)));
-        assertNull(user0.getAllLaunchersForTest().get(PackageWithUser.of(USER_P0, LAUNCHER_1)));
+        assertNull(user0.getAllLaunchersForTest().get(UserPackage.of(USER_0, LAUNCHER_3)));
+        assertNull(user0.getAllLaunchersForTest().get(UserPackage.of(USER_P0, LAUNCHER_1)));
 
         doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(any(byte[].class),
                 anyString());
@@ -6146,12 +6146,12 @@
         assertExistsAndShadow(user0.getAllPackagesForTest().get(CALLING_PACKAGE_2));
         assertExistsAndShadow(user0.getAllPackagesForTest().get(CALLING_PACKAGE_3));
         assertExistsAndShadow(user0.getAllLaunchersForTest().get(
-                PackageWithUser.of(USER_0, LAUNCHER_1)));
+                UserPackage.of(USER_0, LAUNCHER_1)));
         assertExistsAndShadow(user0.getAllLaunchersForTest().get(
-                PackageWithUser.of(USER_0, LAUNCHER_2)));
+                UserPackage.of(USER_0, LAUNCHER_2)));
 
-        assertNull(user0.getAllLaunchersForTest().get(PackageWithUser.of(USER_0, LAUNCHER_3)));
-        assertNull(user0.getAllLaunchersForTest().get(PackageWithUser.of(USER_P0, LAUNCHER_1)));
+        assertNull(user0.getAllLaunchersForTest().get(UserPackage.of(USER_0, LAUNCHER_3)));
+        assertNull(user0.getAllLaunchersForTest().get(UserPackage.of(USER_P0, LAUNCHER_1)));
 
         doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(any(byte[].class),
                 anyString());
diff --git a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
index c786784..15fd73c 100644
--- a/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
+++ b/services/tests/servicestests/src/com/android/server/pm/ShortcutManagerTest2.java
@@ -39,6 +39,7 @@
 import android.content.pm.Capability;
 import android.content.pm.CapabilityParams;
 import android.content.pm.ShortcutInfo;
+import android.content.pm.UserPackage;
 import android.content.res.Resources;
 import android.graphics.BitmapFactory;
 import android.graphics.drawable.Icon;
@@ -51,7 +52,6 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.frameworks.servicestests.R;
-import com.android.server.pm.ShortcutUser.PackageWithUser;
 
 import java.io.File;
 import java.io.FileWriter;
@@ -2413,7 +2413,7 @@
             assertWith(mManager.getDynamicShortcuts()).isEmpty();
         });
         // Make package 1 ephemeral.
-        mEphemeralPackages.add(PackageWithUser.of(USER_0, CALLING_PACKAGE_1));
+        mEphemeralPackages.add(UserPackage.of(USER_0, CALLING_PACKAGE_1));
 
         runWithCaller(CALLING_PACKAGE_1, USER_0, () -> {
             assertExpectException(IllegalStateException.class, "Ephemeral apps", () -> {
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
index 8efcc41..8d2cffa 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserPropertiesTest.java
@@ -59,9 +59,11 @@
         final UserProperties defaultProps = new UserProperties.Builder()
                 .setShowInLauncher(21)
                 .setStartWithParent(false)
+                .setShowInSettings(45)
                 .build();
         final UserProperties actualProps = new UserProperties(defaultProps);
         actualProps.setShowInLauncher(14);
+        actualProps.setShowInSettings(32);
 
         // Write the properties to xml.
         final ByteArrayOutputStream baos = new ByteArrayOutputStream();
@@ -99,10 +101,12 @@
         final UserProperties defaultProps = new UserProperties.Builder()
                 .setShowInLauncher(2145)
                 .setStartWithParent(true)
+                .setShowInSettings(3452)
                 .build();
         final UserProperties orig = new UserProperties(defaultProps);
         orig.setShowInLauncher(2841);
         orig.setStartWithParent(false);
+        orig.setShowInSettings(1437);
 
         // Test every permission level. (Currently, it's linear so it's easy.)
         for (int permLevel = 0; permLevel < 4; permLevel++) {
@@ -140,6 +144,9 @@
         assertEqualGetterOrThrows(orig::getStartWithParent, copy::getStartWithParent, exposeAll);
 
         // Items requiring hasManagePermission - put them here using hasManagePermission.
+        assertEqualGetterOrThrows(orig::getShowInSettings, copy::getShowInSettings,
+                hasManagePermission);
+
         // Items requiring hasQueryPermission - put them here using hasQueryPermission.
 
         // Items with no permission requirements.
@@ -182,5 +189,6 @@
         assertThat(expected.getPropertiesPresent()).isEqualTo(actual.getPropertiesPresent());
         assertThat(expected.getShowInLauncher()).isEqualTo(actual.getShowInLauncher());
         assertThat(expected.getStartWithParent()).isEqualTo(actual.getStartWithParent());
+        assertThat(expected.getShowInSettings()).isEqualTo(actual.getShowInSettings());
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
index c1e778d..df2c5dd 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerTest.java
@@ -599,6 +599,7 @@
         // Check that this new user has the expected properties (relative to the defaults)
         // provided that the test caller has the necessary permissions.
         assertThat(userProps.getShowInLauncher()).isEqualTo(typeProps.getShowInLauncher());
+        assertThat(userProps.getShowInSettings()).isEqualTo(typeProps.getShowInSettings());
         assertThrows(SecurityException.class, userProps::getStartWithParent);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
index 9cd97ff3..699601b 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
@@ -20,21 +20,16 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertTrue;
 
-import android.apex.ApexInfo;
 import android.content.Context;
 import android.content.IntentFilter;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PermissionInfo;
-import android.content.pm.SigningDetails;
 import android.content.pm.parsing.FrameworkParsingPackageUtils;
 import android.content.pm.parsing.result.ParseResult;
 import android.content.pm.parsing.result.ParseTypeImpl;
 import android.os.Build;
 import android.os.Bundle;
 import android.os.FileUtils;
-import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
 import android.util.Pair;
 import android.util.SparseIntArray;
@@ -53,7 +48,6 @@
 import com.android.server.pm.pkg.component.ParsedIntentInfo;
 import com.android.server.pm.pkg.component.ParsedPermission;
 import com.android.server.pm.pkg.component.ParsedPermissionUtils;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 
 import com.google.common.truth.Expect;
 
@@ -63,7 +57,6 @@
 
 import java.io.File;
 import java.io.InputStream;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -567,54 +560,6 @@
     }
 
     @Test
-    public void testApexPackageInfoGeneration() throws Exception {
-        String apexModuleName = "com.android.tzdata.apex";
-        File apexFile = copyRawResourceToFile(apexModuleName,
-                R.raw.com_android_tzdata);
-        ApexInfo apexInfo = new ApexInfo();
-        apexInfo.isActive = true;
-        apexInfo.isFactory = false;
-        apexInfo.moduleName = apexModuleName;
-        apexInfo.modulePath = apexFile.getPath();
-        apexInfo.versionCode = 191000070;
-        int flags = PackageManager.GET_META_DATA | PackageManager.GET_SIGNING_CERTIFICATES;
-
-        ParseResult<ParsedPackage> result = ParsingPackageUtils.parseDefaultOneTime(apexFile,
-                flags, Collections.emptyList(), false /*collectCertificates*/);
-        if (result.isError()) {
-            throw new IllegalStateException(result.getErrorMessage(), result.getException());
-        }
-
-        ParseTypeImpl input = ParseTypeImpl.forDefaultParsing();
-        ParsedPackage pkg = result.getResult();
-        ParseResult<SigningDetails> ret = ParsingPackageUtils.getSigningDetails(
-                input, pkg, false /*skipVerify*/);
-        if (ret.isError()) {
-            throw new IllegalStateException(ret.getErrorMessage(), ret.getException());
-        }
-        pkg.setSigningDetails(ret.getResult());
-        PackageInfo pi = PackageInfoUtils.generate(pkg.setApex(true).hideAsFinal(), apexInfo,
-                flags, null, UserHandle.USER_SYSTEM);
-
-        assertEquals("com.google.android.tzdata", pi.applicationInfo.packageName);
-        assertTrue(pi.applicationInfo.enabled);
-        assertEquals(28, pi.applicationInfo.targetSdkVersion);
-        assertEquals(191000070, pi.applicationInfo.longVersionCode);
-        assertNotNull(pi.applicationInfo.metaData);
-        assertEquals(apexFile.getPath(), pi.applicationInfo.sourceDir);
-        assertEquals("Bundle[{com.android.vending.derived.apk.id=1}]",
-                pi.applicationInfo.metaData.toString());
-
-        assertEquals("com.google.android.tzdata", pi.packageName);
-        assertEquals(191000070, pi.getLongVersionCode());
-        assertNotNull(pi.signingInfo);
-        assertTrue(pi.signingInfo.getApkContentsSigners().length > 0);
-        assertTrue(pi.isApex);
-        assertTrue((pi.applicationInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0);
-        assertTrue((pi.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) != 0);
-    }
-
-    @Test
     public void testUsesSdk() throws Exception {
         ParsedPackage pkg;
         SparseIntArray minExtVers;
diff --git a/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java b/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java
index 7731a32..c2556e9 100644
--- a/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/stats/CpuWakeupStatsTest.java
@@ -44,7 +44,9 @@
 public class CpuWakeupStatsTest {
     private static final String KERNEL_REASON_ALARM_IRQ = "120 test.alarm.device";
     private static final String KERNEL_REASON_UNKNOWN_IRQ = "140 test.unknown.device";
-    private static final String KERNEL_REASON_UNKNOWN = "unsupported-free-form-reason";
+    private static final String KERNEL_REASON_UNKNOWN = "free-form-reason test.alarm.device";
+    private static final String KERNEL_REASON_UNSUPPORTED = "-1 test.alarm.device";
+    private static final String KERNEL_REASON_ABORT = "Abort: due to test.alarm.device";
 
     private static final int TEST_UID_1 = 13239823;
     private static final int TEST_UID_2 = 25268423;
@@ -57,6 +59,7 @@
 
     @Test
     public void removesOldWakeups() {
+        // The xml resource doesn't matter for this test.
         final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_1);
 
         final Set<Long> timestamps = new HashSet<>();
@@ -165,11 +168,36 @@
 
         obj.noteWakeupTimeAndReason(wakeupTime, 34, KERNEL_REASON_UNKNOWN);
 
-        // Unrelated subsystems, should be ignored.
+        // Should be ignored as this type of wakeup is unsupported.
         obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime + 5, TEST_UID_3);
         obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime - 3, TEST_UID_4);
 
         // There should be nothing in the attribution map.
         assertThat(obj.mWakeupAttribution.size()).isEqualTo(0);
     }
+
+    @Test
+    public void unsupportedAttribution() {
+        final CpuWakeupStats obj = new CpuWakeupStats(sContext, R.xml.irq_device_map_3);
+
+        long wakeupTime = 970934;
+        obj.noteWakeupTimeAndReason(wakeupTime, 34, KERNEL_REASON_UNSUPPORTED);
+
+        // Should be ignored as this type of wakeup is unsupported.
+        obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime + 5, TEST_UID_3);
+        obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime - 3, TEST_UID_4);
+
+        // There should be nothing in the attribution map.
+        assertThat(obj.mWakeupAttribution.size()).isEqualTo(0);
+
+        wakeupTime = 883124;
+        obj.noteWakeupTimeAndReason(wakeupTime, 3, KERNEL_REASON_ABORT);
+
+        // Should be ignored as this type of wakeup is unsupported.
+        obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime + 2, TEST_UID_1, TEST_UID_4);
+        obj.noteWakingActivity(CPU_WAKEUP_SUBSYSTEM_ALARM, wakeupTime - 5, TEST_UID_3);
+
+        // There should be nothing in the attribution map.
+        assertThat(obj.mWakeupAttribution.size()).isEqualTo(0);
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/FakeServiceConfigAccessor.java b/services/tests/servicestests/src/com/android/server/timedetector/FakeServiceConfigAccessor.java
index a98a43b..93464cd 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/FakeServiceConfigAccessor.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/FakeServiceConfigAccessor.java
@@ -25,7 +25,7 @@
 import android.app.time.TimeCapabilitiesAndConfig;
 import android.app.time.TimeConfiguration;
 
-import com.android.server.timezonedetector.ConfigurationChangeListener;
+import com.android.server.timezonedetector.StateChangeListener;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -33,17 +33,17 @@
 /** A partially implemented, fake implementation of ServiceConfigAccessor for tests. */
 public class FakeServiceConfigAccessor implements ServiceConfigAccessor {
 
-    private final List<ConfigurationChangeListener> mConfigurationInternalChangeListeners =
+    private final List<StateChangeListener> mConfigurationInternalChangeListeners =
             new ArrayList<>();
     private ConfigurationInternal mConfigurationInternal;
 
     @Override
-    public void addConfigurationInternalChangeListener(ConfigurationChangeListener listener) {
+    public void addConfigurationInternalChangeListener(StateChangeListener listener) {
         mConfigurationInternalChangeListeners.add(listener);
     }
 
     @Override
-    public void removeConfigurationInternalChangeListener(ConfigurationChangeListener listener) {
+    public void removeConfigurationInternalChangeListener(StateChangeListener listener) {
         mConfigurationInternalChangeListeners.remove(listener);
     }
 
@@ -86,7 +86,7 @@
     }
 
     void simulateConfigurationChangeForTests() {
-        for (ConfigurationChangeListener listener : mConfigurationInternalChangeListeners) {
+        for (StateChangeListener listener : mConfigurationInternalChangeListeners) {
             listener.onChange();
         }
     }
diff --git a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
index 62dae48..caef494 100644
--- a/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timedetector/TimeDetectorStrategyImplTest.java
@@ -40,7 +40,7 @@
 
 import com.android.server.SystemClockTime.TimeConfidence;
 import com.android.server.timedetector.TimeDetectorStrategy.Origin;
-import com.android.server.timezonedetector.ConfigurationChangeListener;
+import com.android.server.timezonedetector.StateChangeListener;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -1821,7 +1821,7 @@
         private long mElapsedRealtimeMillis;
         private long mSystemClockMillis;
         private int mSystemClockConfidence = TIME_CONFIDENCE_LOW;
-        private ConfigurationChangeListener mConfigurationInternalChangeListener;
+        private StateChangeListener mConfigurationInternalChangeListener;
 
         // Tracking operations.
         private boolean mSystemClockWasSet;
@@ -1837,7 +1837,7 @@
         }
 
         @Override
-        public void setConfigurationInternalChangeListener(ConfigurationChangeListener listener) {
+        public void setConfigurationInternalChangeListener(StateChangeListener listener) {
             mConfigurationInternalChangeListener = Objects.requireNonNull(listener);
         }
 
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
index 7140097..153d746 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/ConfigurationInternalTest.java
@@ -57,7 +57,8 @@
     @Parameters({ "true,true", "true,false", "false,true", "false,false" })
     public void test_autoDetectionSupported_capabilitiesAndConfiguration(
             boolean userConfigAllowed, boolean bypassUserPolicyChecks) {
-        ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+        ConfigurationInternal baseConfig = new ConfigurationInternal.Builder()
+                .setUserId(ARBITRARY_USER_ID)
                 .setUserConfigAllowed(userConfigAllowed)
                 .setTelephonyDetectionFeatureSupported(true)
                 .setGeoDetectionFeatureSupported(true)
@@ -82,10 +83,7 @@
             assertTrue(autoOnConfig.isGeoDetectionExecutionEnabled());
             assertEquals(DETECTION_MODE_GEO, autoOnConfig.getDetectionMode());
 
-            TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
-                    autoOnConfig.createCapabilitiesAndConfig(bypassUserPolicyChecks);
-
-            TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+            TimeZoneCapabilities capabilities = autoOnConfig.asCapabilities(bypassUserPolicyChecks);
             if (userRestrictionsExpected) {
                 assertEquals(CAPABILITY_NOT_ALLOWED,
                         capabilities.getConfigureAutoDetectionEnabledCapability());
@@ -101,7 +99,7 @@
             assertEquals(CAPABILITY_POSSESSED,
                     capabilities.getConfigureGeoDetectionEnabledCapability());
 
-            TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+            TimeZoneConfiguration configuration = autoOnConfig.asConfiguration();
             assertTrue(configuration.isAutoDetectionEnabled());
             assertTrue(configuration.isGeoDetectionEnabled());
         }
@@ -117,10 +115,8 @@
             assertFalse(autoOffConfig.isGeoDetectionExecutionEnabled());
             assertEquals(DETECTION_MODE_MANUAL, autoOffConfig.getDetectionMode());
 
-            TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
-                    autoOffConfig.createCapabilitiesAndConfig(bypassUserPolicyChecks);
-
-            TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+            TimeZoneCapabilities capabilities =
+                    autoOffConfig.asCapabilities(bypassUserPolicyChecks);
             if (userRestrictionsExpected) {
                 assertEquals(CAPABILITY_NOT_ALLOWED,
                         capabilities.getConfigureAutoDetectionEnabledCapability());
@@ -136,7 +132,7 @@
             assertEquals(CAPABILITY_NOT_APPLICABLE,
                     capabilities.getConfigureGeoDetectionEnabledCapability());
 
-            TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+            TimeZoneConfiguration configuration = autoOffConfig.asConfiguration();
             assertFalse(configuration.isAutoDetectionEnabled());
             assertTrue(configuration.isGeoDetectionEnabled());
         }
@@ -150,7 +146,8 @@
     @Parameters({ "true,true", "true,false", "false,true", "false,false" })
     public void test_autoDetectNotSupported_capabilitiesAndConfiguration(
             boolean userConfigAllowed, boolean bypassUserPolicyChecks) {
-        ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+        ConfigurationInternal baseConfig = new ConfigurationInternal.Builder()
+                .setUserId(ARBITRARY_USER_ID)
                 .setUserConfigAllowed(userConfigAllowed)
                 .setTelephonyDetectionFeatureSupported(false)
                 .setGeoDetectionFeatureSupported(false)
@@ -175,10 +172,7 @@
             assertFalse(autoOnConfig.isGeoDetectionExecutionEnabled());
             assertEquals(DETECTION_MODE_MANUAL, autoOnConfig.getDetectionMode());
 
-            TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
-                    autoOnConfig.createCapabilitiesAndConfig(bypassUserPolicyChecks);
-
-            TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+            TimeZoneCapabilities capabilities = autoOnConfig.asCapabilities(bypassUserPolicyChecks);
             assertEquals(CAPABILITY_NOT_SUPPORTED,
                     capabilities.getConfigureAutoDetectionEnabledCapability());
             if (userRestrictionsExpected) {
@@ -189,7 +183,7 @@
             assertEquals(CAPABILITY_NOT_SUPPORTED,
                     capabilities.getConfigureGeoDetectionEnabledCapability());
 
-            TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+            TimeZoneConfiguration configuration = autoOnConfig.asConfiguration();
             assertTrue(configuration.isAutoDetectionEnabled());
             assertTrue(configuration.isGeoDetectionEnabled());
         }
@@ -205,10 +199,8 @@
             assertFalse(autoOffConfig.isGeoDetectionExecutionEnabled());
             assertEquals(DETECTION_MODE_MANUAL, autoOffConfig.getDetectionMode());
 
-            TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
-                    autoOffConfig.createCapabilitiesAndConfig(bypassUserPolicyChecks);
-
-            TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+            TimeZoneCapabilities capabilities =
+                    autoOffConfig.asCapabilities(bypassUserPolicyChecks);
             assertEquals(CAPABILITY_NOT_SUPPORTED,
                     capabilities.getConfigureAutoDetectionEnabledCapability());
             if (userRestrictionsExpected) {
@@ -219,7 +211,7 @@
             assertEquals(CAPABILITY_NOT_SUPPORTED,
                     capabilities.getConfigureGeoDetectionEnabledCapability());
 
-            TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+            TimeZoneConfiguration configuration = autoOffConfig.asConfiguration();
             assertFalse(configuration.isAutoDetectionEnabled());
             assertTrue(configuration.isGeoDetectionEnabled());
         }
@@ -233,7 +225,8 @@
     @Parameters({ "true,true", "true,false", "false,true", "false,false" })
     public void test_geoDetectNotSupported_capabilitiesAndConfiguration(
             boolean userConfigAllowed, boolean bypassUserPolicyChecks) {
-        ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+        ConfigurationInternal baseConfig = new ConfigurationInternal.Builder()
+                .setUserId(ARBITRARY_USER_ID)
                 .setUserConfigAllowed(userConfigAllowed)
                 .setTelephonyDetectionFeatureSupported(true)
                 .setGeoDetectionFeatureSupported(false)
@@ -258,10 +251,7 @@
             assertFalse(autoOnConfig.isGeoDetectionExecutionEnabled());
             assertEquals(DETECTION_MODE_TELEPHONY, autoOnConfig.getDetectionMode());
 
-            TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
-                    autoOnConfig.createCapabilitiesAndConfig(bypassUserPolicyChecks);
-
-            TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+            TimeZoneCapabilities capabilities = autoOnConfig.asCapabilities(bypassUserPolicyChecks);
             if (userRestrictionsExpected) {
                 assertEquals(CAPABILITY_NOT_ALLOWED,
                         capabilities.getConfigureAutoDetectionEnabledCapability());
@@ -276,7 +266,7 @@
             assertEquals(CAPABILITY_NOT_SUPPORTED,
                     capabilities.getConfigureGeoDetectionEnabledCapability());
 
-            TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+            TimeZoneConfiguration configuration = autoOnConfig.asConfiguration();
             assertTrue(configuration.isAutoDetectionEnabled());
             assertTrue(configuration.isGeoDetectionEnabled());
         }
@@ -292,10 +282,8 @@
             assertFalse(autoOffConfig.isGeoDetectionExecutionEnabled());
             assertEquals(DETECTION_MODE_MANUAL, autoOffConfig.getDetectionMode());
 
-            TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
-                    autoOffConfig.createCapabilitiesAndConfig(bypassUserPolicyChecks);
-
-            TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
+            TimeZoneCapabilities capabilities =
+                    autoOffConfig.asCapabilities(bypassUserPolicyChecks);
             if (userRestrictionsExpected) {
                 assertEquals(CAPABILITY_NOT_ALLOWED,
                         capabilities.getConfigureAutoDetectionEnabledCapability());
@@ -308,7 +296,7 @@
             assertEquals(CAPABILITY_NOT_SUPPORTED,
                     capabilities.getConfigureGeoDetectionEnabledCapability());
 
-            TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+            TimeZoneConfiguration configuration = autoOffConfig.asConfiguration();
             assertFalse(configuration.isAutoDetectionEnabled());
             assertTrue(configuration.isGeoDetectionEnabled());
         }
@@ -316,7 +304,8 @@
 
     @Test
     public void test_telephonyFallbackSupported() {
-        ConfigurationInternal config = new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+        ConfigurationInternal config = new ConfigurationInternal.Builder()
+                .setUserId(ARBITRARY_USER_ID)
                 .setUserConfigAllowed(true)
                 .setTelephonyDetectionFeatureSupported(true)
                 .setGeoDetectionFeatureSupported(false)
@@ -331,7 +320,8 @@
     /** Tests when {@link ConfigurationInternal#getGeoDetectionRunInBackgroundEnabled()} is true. */
     @Test
     public void test_geoDetectionRunInBackgroundEnabled() {
-        ConfigurationInternal baseConfig = new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+        ConfigurationInternal baseConfig = new ConfigurationInternal.Builder()
+                .setUserId(ARBITRARY_USER_ID)
                 .setUserConfigAllowed(true)
                 .setTelephonyDetectionFeatureSupported(true)
                 .setGeoDetectionFeatureSupported(true)
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeServiceConfigAccessor.java b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeServiceConfigAccessor.java
index fdee86e..fc6afe4 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeServiceConfigAccessor.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeServiceConfigAccessor.java
@@ -16,14 +16,11 @@
 
 package com.android.server.timezonedetector;
 
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.fail;
 
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
 import android.app.time.TimeZoneCapabilities;
-import android.app.time.TimeZoneCapabilitiesAndConfig;
 import android.app.time.TimeZoneConfiguration;
 
 import java.time.Duration;
@@ -31,76 +28,104 @@
 import java.util.List;
 import java.util.Optional;
 
-/** A partially implemented, fake implementation of ServiceConfigAccessor for tests. */
+/**
+ * A partially implemented, fake implementation of ServiceConfigAccessor for tests.
+ *
+ * <p>This class has rudamentary support for multiple users, but unlike the real thing, it doesn't
+ * simulate that some settings are global and shared between users. It also delivers config updates
+ * synchronously.
+ */
 public class FakeServiceConfigAccessor implements ServiceConfigAccessor {
 
-    private final List<ConfigurationChangeListener> mConfigurationInternalChangeListeners =
+    private final List<StateChangeListener> mConfigurationInternalChangeListeners =
             new ArrayList<>();
-    private ConfigurationInternal mConfigurationInternal;
+    private ConfigurationInternal mCurrentUserConfigurationInternal;
+    private ConfigurationInternal mOtherUserConfigurationInternal;
 
     @Override
-    public void addConfigurationInternalChangeListener(ConfigurationChangeListener listener) {
+    public void addConfigurationInternalChangeListener(StateChangeListener listener) {
         mConfigurationInternalChangeListeners.add(listener);
     }
 
     @Override
-    public void removeConfigurationInternalChangeListener(ConfigurationChangeListener listener) {
+    public void removeConfigurationInternalChangeListener(StateChangeListener listener) {
         mConfigurationInternalChangeListeners.remove(listener);
     }
 
     @Override
     public ConfigurationInternal getCurrentUserConfigurationInternal() {
-        return mConfigurationInternal;
+        return getConfigurationInternal(mCurrentUserConfigurationInternal.getUserId());
     }
 
     @Override
     public boolean updateConfiguration(
-            @UserIdInt int userID, @NonNull TimeZoneConfiguration requestedChanges,
+            @UserIdInt int userId, @NonNull TimeZoneConfiguration requestedChanges,
             boolean bypassUserPolicyChecks) {
-        assertNotNull(mConfigurationInternal);
+        assertNotNull(mCurrentUserConfigurationInternal);
         assertNotNull(requestedChanges);
 
+        ConfigurationInternal toUpdate = getConfigurationInternal(userId);
+
         // Simulate the real strategy's behavior: the new configuration will be updated to be the
-        // old configuration merged with the new if the user has the capability to up the settings.
-        // Then, if the configuration changed, the change listener is invoked.
-        TimeZoneCapabilitiesAndConfig capabilitiesAndConfig =
-                mConfigurationInternal.createCapabilitiesAndConfig(bypassUserPolicyChecks);
-        TimeZoneCapabilities capabilities = capabilitiesAndConfig.getCapabilities();
-        TimeZoneConfiguration configuration = capabilitiesAndConfig.getConfiguration();
+        // old configuration merged with the new if the user has the capability to update the
+        // settings. Then, if the configuration changed, the change listener is invoked.
+        TimeZoneCapabilities capabilities = toUpdate.asCapabilities(bypassUserPolicyChecks);
+        TimeZoneConfiguration configuration = toUpdate.asConfiguration();
         TimeZoneConfiguration newConfiguration =
                 capabilities.tryApplyConfigChanges(configuration, requestedChanges);
         if (newConfiguration == null) {
             return false;
         }
 
-        if (!newConfiguration.equals(capabilitiesAndConfig.getConfiguration())) {
-            mConfigurationInternal = mConfigurationInternal.merge(newConfiguration);
-
+        if (!newConfiguration.equals(configuration)) {
+            ConfigurationInternal updatedConfiguration = toUpdate.merge(newConfiguration);
+            if (updatedConfiguration.getUserId() == mCurrentUserConfigurationInternal.getUserId()) {
+                mCurrentUserConfigurationInternal = updatedConfiguration;
+            } else if (mOtherUserConfigurationInternal != null
+                    && updatedConfiguration.getUserId()
+                    == mOtherUserConfigurationInternal.getUserId()) {
+                mOtherUserConfigurationInternal = updatedConfiguration;
+            }
             // Note: Unlike the real strategy, the listeners are invoked synchronously.
-            simulateConfigurationChangeForTests();
+            notifyConfigurationChange();
         }
         return true;
     }
 
-    void initializeConfiguration(ConfigurationInternal configurationInternal) {
-        mConfigurationInternal = configurationInternal;
+    void initializeCurrentUserConfiguration(ConfigurationInternal configurationInternal) {
+        mCurrentUserConfigurationInternal = configurationInternal;
     }
 
-    void simulateConfigurationChangeForTests() {
-        for (ConfigurationChangeListener listener : mConfigurationInternalChangeListeners) {
-            listener.onChange();
-        }
+    void initializeOtherUserConfiguration(ConfigurationInternal configurationInternal) {
+        mOtherUserConfigurationInternal = configurationInternal;
+    }
+
+    void simulateCurrentUserConfigurationInternalChange(
+            ConfigurationInternal configurationInternal) {
+        mCurrentUserConfigurationInternal = configurationInternal;
+        // Note: Unlike the real strategy, the listeners are invoked synchronously.
+        notifyConfigurationChange();
+    }
+
+    void simulateOtherUserConfigurationInternalChange(ConfigurationInternal configurationInternal) {
+        mOtherUserConfigurationInternal = configurationInternal;
+        // Note: Unlike the real strategy, the listeners are invoked synchronously.
+        notifyConfigurationChange();
     }
 
     @Override
     public ConfigurationInternal getConfigurationInternal(int userId) {
-        assertEquals("Multi-user testing not supported currently",
-                userId, mConfigurationInternal.getUserId());
-        return mConfigurationInternal;
+        if (userId == mCurrentUserConfigurationInternal.getUserId()) {
+            return mCurrentUserConfigurationInternal;
+        } else if (mOtherUserConfigurationInternal != null
+                    && userId == mOtherUserConfigurationInternal.getUserId()) {
+            return mOtherUserConfigurationInternal;
+        }
+        throw new AssertionError("userId not known: " + userId);
     }
 
     @Override
-    public void addLocationTimeZoneManagerConfigListener(ConfigurationChangeListener listener) {
+    public void addLocationTimeZoneManagerConfigListener(StateChangeListener listener) {
         failUnimplemented();
     }
 
@@ -206,9 +231,14 @@
         failUnimplemented();
     }
 
+    private void notifyConfigurationChange() {
+        for (StateChangeListener listener : mConfigurationInternalChangeListeners) {
+            listener.onChange();
+        }
+    }
+
     @SuppressWarnings("UnusedReturnValue")
     private static <T> T failUnimplemented() {
-        fail("Unimplemented");
-        return null;
+        throw new AssertionError("Unimplemented");
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
index 228dc95..fed8b40 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/FakeTimeZoneDetectorStrategy.java
@@ -15,23 +15,71 @@
  */
 package com.android.server.timezonedetector;
 
+import static org.junit.Assert.assertEquals;
+
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
+import android.app.time.TimeZoneCapabilitiesAndConfig;
+import android.app.time.TimeZoneConfiguration;
 import android.app.time.TimeZoneState;
 import android.app.timezonedetector.ManualTimeZoneSuggestion;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
 import android.util.IndentingPrintWriter;
 
+import java.util.ArrayList;
+
 public class FakeTimeZoneDetectorStrategy implements TimeZoneDetectorStrategy {
 
+    private final FakeServiceConfigAccessor mFakeServiceConfigAccessor =
+            new FakeServiceConfigAccessor();
+    private final ArrayList<StateChangeListener> mListeners = new ArrayList<>();
     private TimeZoneState mTimeZoneState;
 
+    public FakeTimeZoneDetectorStrategy() {
+        mFakeServiceConfigAccessor.addConfigurationInternalChangeListener(
+                this::notifyChangeListeners);
+    }
+
+    public void initializeConfiguration(ConfigurationInternal configuration) {
+        mFakeServiceConfigAccessor.initializeCurrentUserConfiguration(configuration);
+    }
+
     @Override
     public boolean confirmTimeZone(String timeZoneId) {
         return false;
     }
 
     @Override
+    public TimeZoneCapabilitiesAndConfig getCapabilitiesAndConfig(int userId,
+            boolean bypassUserPolicyChecks) {
+        ConfigurationInternal configurationInternal =
+                mFakeServiceConfigAccessor.getCurrentUserConfigurationInternal();
+        assertEquals("Multi-user testing not supported",
+                configurationInternal.getUserId(), userId);
+        return new TimeZoneCapabilitiesAndConfig(
+                configurationInternal.asCapabilities(bypassUserPolicyChecks),
+                configurationInternal.asConfiguration());
+    }
+
+    @Override
+    public boolean updateConfiguration(int userId, TimeZoneConfiguration requestedChanges,
+            boolean bypassUserPolicyChecks) {
+        return mFakeServiceConfigAccessor.updateConfiguration(
+                userId, requestedChanges, bypassUserPolicyChecks);
+    }
+
+    @Override
+    public void addChangeListener(StateChangeListener listener) {
+        mListeners.add(listener);
+    }
+
+    private void notifyChangeListeners() {
+        for (StateChangeListener listener : mListeners) {
+            listener.onChange();
+        }
+    }
+
+    @Override
     public TimeZoneState getTimeZoneState() {
         return mTimeZoneState;
     }
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java
index 782eebf..223c532 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/MetricsTimeZoneDetectorStateTest.java
@@ -161,7 +161,8 @@
 
     private static ConfigurationInternal createConfigurationInternal(
             boolean enhancedMetricsCollectionEnabled) {
-        return new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+        return new ConfigurationInternal.Builder()
+                .setUserId(ARBITRARY_USER_ID)
                 .setUserConfigAllowed(true)
                 .setTelephonyDetectionFeatureSupported(true)
                 .setGeoDetectionFeatureSupported(true)
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TestHandler.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TestHandler.java
index 21c9685..eb6f00c 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TestHandler.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TestHandler.java
@@ -66,10 +66,14 @@
     /**
      * Waits for all enqueued work to be completed before returning.
      */
-    public void waitForMessagesToBeProcessed() throws InterruptedException {
+    public void waitForMessagesToBeProcessed() {
         synchronized (mMonitor) {
             if (mMessagesSent != mMessagesProcessed) {
-                mMonitor.wait();
+                try {
+                    mMonitor.wait();
+                } catch (InterruptedException e) {
+                    throw new AssertionError("Unexpected exception", e);
+                }
             }
         }
     }
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java
index 276fdb9..8909832 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorInternalImplTest.java
@@ -50,7 +50,6 @@
     private HandlerThread mHandlerThread;
     private TestHandler mTestHandler;
     private TestCurrentUserIdentityInjector mTestCurrentUserIdentityInjector;
-    private FakeServiceConfigAccessor mFakeServiceConfigAccessorSpy;
     private FakeTimeZoneDetectorStrategy mFakeTimeZoneDetectorStrategySpy;
 
     private TimeZoneDetectorInternalImpl mTimeZoneDetectorInternal;
@@ -65,12 +64,11 @@
         mTestHandler = new TestHandler(mHandlerThread.getLooper());
         mTestCurrentUserIdentityInjector = new TestCurrentUserIdentityInjector();
         mTestCurrentUserIdentityInjector.initializeCurrentUserId(ARBITRARY_USER_ID);
-        mFakeServiceConfigAccessorSpy = spy(new FakeServiceConfigAccessor());
         mFakeTimeZoneDetectorStrategySpy = spy(new FakeTimeZoneDetectorStrategy());
 
         mTimeZoneDetectorInternal = new TimeZoneDetectorInternalImpl(
                 mMockContext, mTestHandler, mTestCurrentUserIdentityInjector,
-                mFakeServiceConfigAccessorSpy, mFakeTimeZoneDetectorStrategySpy);
+                mFakeTimeZoneDetectorStrategySpy);
     }
 
     @After
@@ -83,17 +81,20 @@
     public void testGetCapabilitiesAndConfigForDpm() throws Exception {
         final boolean autoDetectionEnabled = true;
         ConfigurationInternal testConfig = createConfigurationInternal(autoDetectionEnabled);
-        mFakeServiceConfigAccessorSpy.initializeConfiguration(testConfig);
+        mFakeTimeZoneDetectorStrategySpy.initializeConfiguration(testConfig);
 
         TimeZoneCapabilitiesAndConfig actualCapabilitiesAndConfig =
                 mTimeZoneDetectorInternal.getCapabilitiesAndConfigForDpm();
 
         int expectedUserId = mTestCurrentUserIdentityInjector.getCurrentUserId();
-        verify(mFakeServiceConfigAccessorSpy).getConfigurationInternal(expectedUserId);
+        final boolean expectedBypassUserPolicyChecks = true;
+        verify(mFakeTimeZoneDetectorStrategySpy).getCapabilitiesAndConfig(
+                expectedUserId, expectedBypassUserPolicyChecks);
 
-        final boolean bypassUserPolicyChecks = true;
         TimeZoneCapabilitiesAndConfig expectedCapabilitiesAndConfig =
-                testConfig.createCapabilitiesAndConfig(bypassUserPolicyChecks);
+                new TimeZoneCapabilitiesAndConfig(
+                        testConfig.asCapabilities(expectedBypassUserPolicyChecks),
+                        testConfig.asConfiguration());
         assertEquals(expectedCapabilitiesAndConfig, actualCapabilitiesAndConfig);
     }
 
@@ -102,7 +103,7 @@
         final boolean autoDetectionEnabled = false;
         ConfigurationInternal initialConfigurationInternal =
                 createConfigurationInternal(autoDetectionEnabled);
-        mFakeServiceConfigAccessorSpy.initializeConfiguration(initialConfigurationInternal);
+        mFakeTimeZoneDetectorStrategySpy.initializeConfiguration(initialConfigurationInternal);
 
         TimeZoneConfiguration timeConfiguration = new TimeZoneConfiguration.Builder()
                 .setAutoDetectionEnabled(true)
@@ -110,7 +111,7 @@
         assertTrue(mTimeZoneDetectorInternal.updateConfigurationForDpm(timeConfiguration));
 
         final boolean expectedBypassUserPolicyChecks = true;
-        verify(mFakeServiceConfigAccessorSpy).updateConfiguration(
+        verify(mFakeTimeZoneDetectorStrategySpy).updateConfiguration(
                 mTestCurrentUserIdentityInjector.getCurrentUserId(),
                 timeConfiguration,
                 expectedBypassUserPolicyChecks);
@@ -148,7 +149,8 @@
     }
 
     private static ConfigurationInternal createConfigurationInternal(boolean autoDetectionEnabled) {
-        return new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+        return new ConfigurationInternal.Builder()
+                .setUserId(ARBITRARY_USER_ID)
                 .setTelephonyDetectionFeatureSupported(true)
                 .setGeoDetectionFeatureSupported(true)
                 .setTelephonyFallbackSupported(false)
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
index bb9d564..d8346ee 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorServiceTest.java
@@ -69,7 +69,6 @@
     private HandlerThread mHandlerThread;
     private TestHandler mTestHandler;
     private TestCallerIdentityInjector mTestCallerIdentityInjector;
-    private FakeServiceConfigAccessor mFakeServiceConfigAccessorSpy;
     private FakeTimeZoneDetectorStrategy mFakeTimeZoneDetectorStrategySpy;
 
 
@@ -85,12 +84,11 @@
         mTestCallerIdentityInjector = new TestCallerIdentityInjector();
         mTestCallerIdentityInjector.initializeCallingUserId(ARBITRARY_USER_ID);
 
-        mFakeServiceConfigAccessorSpy = spy(new FakeServiceConfigAccessor());
         mFakeTimeZoneDetectorStrategySpy = spy(new FakeTimeZoneDetectorStrategy());
 
         mTimeZoneDetectorService = new TimeZoneDetectorService(
                 mMockContext, mTestHandler, mTestCallerIdentityInjector,
-                mFakeServiceConfigAccessorSpy, mFakeTimeZoneDetectorStrategySpy);
+                mFakeTimeZoneDetectorStrategySpy);
     }
 
     @After
@@ -115,7 +113,7 @@
 
         ConfigurationInternal configuration =
                 createConfigurationInternal(true /* autoDetectionEnabled*/);
-        mFakeServiceConfigAccessorSpy.initializeConfiguration(configuration);
+        mFakeTimeZoneDetectorStrategySpy.initializeConfiguration(configuration);
 
         TimeZoneCapabilitiesAndConfig actualCapabilitiesAndConfig =
                 mTimeZoneDetectorService.getCapabilitiesAndConfig();
@@ -124,11 +122,14 @@
                 eq(android.Manifest.permission.MANAGE_TIME_AND_ZONE_DETECTION), anyString());
 
         int expectedUserId = mTestCallerIdentityInjector.getCallingUserId();
-        verify(mFakeServiceConfigAccessorSpy).getConfigurationInternal(expectedUserId);
-
         boolean expectedBypassUserPolicyChecks = false;
+        verify(mFakeTimeZoneDetectorStrategySpy)
+                .getCapabilitiesAndConfig(expectedUserId, expectedBypassUserPolicyChecks);
+
         TimeZoneCapabilitiesAndConfig expectedCapabilitiesAndConfig =
-                configuration.createCapabilitiesAndConfig(expectedBypassUserPolicyChecks);
+                new TimeZoneCapabilitiesAndConfig(
+                        configuration.asCapabilities(expectedBypassUserPolicyChecks),
+                        configuration.asConfiguration());
         assertEquals(expectedCapabilitiesAndConfig, actualCapabilitiesAndConfig);
     }
 
@@ -160,7 +161,7 @@
     public void testListenerRegistrationAndCallbacks() throws Exception {
         ConfigurationInternal initialConfiguration =
                 createConfigurationInternal(false /* autoDetectionEnabled */);
-        mFakeServiceConfigAccessorSpy.initializeConfiguration(initialConfiguration);
+        mFakeTimeZoneDetectorStrategySpy.initializeConfiguration(initialConfiguration);
 
         IBinder mockListenerBinder = mock(IBinder.class);
         ITimeZoneDetectorListener mockListener = mock(ITimeZoneDetectorListener.class);
@@ -455,7 +456,8 @@
         // Default geo detection settings from auto detection settings - they are not important to
         // the tests.
         final boolean geoDetectionEnabled = autoDetectionEnabled;
-        return new ConfigurationInternal.Builder(ARBITRARY_USER_ID)
+        return new ConfigurationInternal.Builder()
+                .setUserId(ARBITRARY_USER_ID)
                 .setTelephonyDetectionFeatureSupported(true)
                 .setGeoDetectionFeatureSupported(true)
                 .setTelephonyFallbackSupported(false)
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
index d0a7c92..f50e7fb 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/TimeZoneDetectorStrategyImplTest.java
@@ -38,19 +38,28 @@
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
 
 import android.annotation.ElapsedRealtimeLong;
 import android.annotation.NonNull;
 import android.annotation.UserIdInt;
+import android.app.time.TimeZoneCapabilitiesAndConfig;
+import android.app.time.TimeZoneConfiguration;
 import android.app.time.TimeZoneState;
 import android.app.timezonedetector.ManualTimeZoneSuggestion;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion.MatchType;
 import android.app.timezonedetector.TelephonyTimeZoneSuggestion.Quality;
+import android.os.HandlerThread;
 
 import com.android.server.SystemTimeZone.TimeZoneConfidence;
 import com.android.server.timezonedetector.TimeZoneDetectorStrategyImpl.QualifiedTelephonyTimeZoneSuggestion;
 
+import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -97,7 +106,8 @@
     };
 
     private static final ConfigurationInternal CONFIG_USER_RESTRICTED_AUTO_DISABLED =
-            new ConfigurationInternal.Builder(USER_ID)
+            new ConfigurationInternal.Builder()
+                    .setUserId(USER_ID)
                     .setTelephonyDetectionFeatureSupported(true)
                     .setGeoDetectionFeatureSupported(true)
                     .setTelephonyFallbackSupported(false)
@@ -110,7 +120,8 @@
                     .build();
 
     private static final ConfigurationInternal CONFIG_USER_RESTRICTED_AUTO_ENABLED =
-            new ConfigurationInternal.Builder(USER_ID)
+            new ConfigurationInternal.Builder()
+                    .setUserId(USER_ID)
                     .setTelephonyDetectionFeatureSupported(true)
                     .setGeoDetectionFeatureSupported(true)
                     .setTelephonyFallbackSupported(false)
@@ -123,7 +134,8 @@
                     .build();
 
     private static final ConfigurationInternal CONFIG_AUTO_DETECT_NOT_SUPPORTED =
-            new ConfigurationInternal.Builder(USER_ID)
+            new ConfigurationInternal.Builder()
+                    .setUserId(USER_ID)
                     .setTelephonyDetectionFeatureSupported(false)
                     .setGeoDetectionFeatureSupported(false)
                     .setTelephonyFallbackSupported(false)
@@ -136,7 +148,8 @@
                     .build();
 
     private static final ConfigurationInternal CONFIG_AUTO_DISABLED_GEO_DISABLED =
-            new ConfigurationInternal.Builder(USER_ID)
+            new ConfigurationInternal.Builder()
+                    .setUserId(USER_ID)
                     .setTelephonyDetectionFeatureSupported(true)
                     .setGeoDetectionFeatureSupported(true)
                     .setTelephonyFallbackSupported(false)
@@ -149,7 +162,8 @@
                     .build();
 
     private static final ConfigurationInternal CONFIG_AUTO_ENABLED_GEO_DISABLED =
-            new ConfigurationInternal.Builder(USER_ID)
+            new ConfigurationInternal.Builder()
+                    .setUserId(USER_ID)
                     .setTelephonyDetectionFeatureSupported(true)
                     .setGeoDetectionFeatureSupported(true)
                     .setTelephonyFallbackSupported(false)
@@ -162,7 +176,8 @@
                     .build();
 
     private static final ConfigurationInternal CONFIG_AUTO_ENABLED_GEO_ENABLED =
-            new ConfigurationInternal.Builder(USER_ID)
+            new ConfigurationInternal.Builder()
+                    .setUserId(USER_ID)
                     .setTelephonyDetectionFeatureSupported(true)
                     .setGeoDetectionFeatureSupported(true)
                     .setTelephonyFallbackSupported(false)
@@ -174,14 +189,223 @@
                     .setGeoDetectionEnabledSetting(true)
                     .build();
 
-    private TimeZoneDetectorStrategyImpl mTimeZoneDetectorStrategy;
+    private FakeServiceConfigAccessor mFakeServiceConfigAccessorSpy;
     private FakeEnvironment mFakeEnvironment;
+    private HandlerThread mHandlerThread;
+    private TestHandler mTestHandler;
+
+    private TimeZoneDetectorStrategyImpl mTimeZoneDetectorStrategy;
 
     @Before
     public void setUp() {
         mFakeEnvironment = new FakeEnvironment();
-        mFakeEnvironment.initializeConfig(CONFIG_AUTO_DISABLED_GEO_DISABLED);
-        mTimeZoneDetectorStrategy = new TimeZoneDetectorStrategyImpl(mFakeEnvironment);
+        mFakeServiceConfigAccessorSpy = spy(new FakeServiceConfigAccessor());
+        mFakeServiceConfigAccessorSpy.initializeCurrentUserConfiguration(
+                CONFIG_AUTO_DISABLED_GEO_DISABLED);
+
+        // Create a thread + handler for processing the work that the strategy posts.
+        mHandlerThread = new HandlerThread("TimeZoneDetectorStrategyImplTest");
+        mHandlerThread.start();
+        mTestHandler = new TestHandler(mHandlerThread.getLooper());
+        mTimeZoneDetectorStrategy = new TimeZoneDetectorStrategyImpl(
+                mFakeServiceConfigAccessorSpy, mTestHandler, mFakeEnvironment);
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        mHandlerThread.quit();
+        mHandlerThread.join();
+    }
+
+    @Test
+    public void testChangeListenerBehavior_currentUser() throws Exception {
+        ConfigurationInternal currentUserConfig = CONFIG_AUTO_DISABLED_GEO_DISABLED;
+        // The strategy initializes itself with the current user's config during construction.
+        assertEquals(currentUserConfig,
+                mTimeZoneDetectorStrategy.getCachedCapabilitiesAndConfigForTests());
+
+        TestStateChangeListener stateChangeListener = new TestStateChangeListener();
+        mTimeZoneDetectorStrategy.addChangeListener(stateChangeListener);
+
+        boolean bypassUserPolicyChecks = false;
+
+        // Report a config change, but not one that actually changes anything.
+        {
+            mFakeServiceConfigAccessorSpy.simulateCurrentUserConfigurationInternalChange(
+                    CONFIG_AUTO_DISABLED_GEO_DISABLED);
+            mTestHandler.waitForMessagesToBeProcessed();
+
+            stateChangeListener.assertNotificationsReceived(0);
+            assertEquals(CONFIG_AUTO_DISABLED_GEO_DISABLED,
+                    mTimeZoneDetectorStrategy.getCachedCapabilitiesAndConfigForTests());
+        }
+
+        // Report a config change that actually changes something.
+        {
+            mFakeServiceConfigAccessorSpy.simulateCurrentUserConfigurationInternalChange(
+                    CONFIG_AUTO_ENABLED_GEO_ENABLED);
+            mTestHandler.waitForMessagesToBeProcessed();
+
+            stateChangeListener.assertNotificationsReceived(1);
+            stateChangeListener.resetNotificationsReceivedCount();
+            assertEquals(CONFIG_AUTO_ENABLED_GEO_ENABLED,
+                    mTimeZoneDetectorStrategy.getCachedCapabilitiesAndConfigForTests());
+        }
+
+        // Perform a (current user) update via the strategy.
+        {
+            TimeZoneConfiguration requestedChanges =
+                    new TimeZoneConfiguration.Builder().setGeoDetectionEnabled(false).build();
+            mTimeZoneDetectorStrategy.updateConfiguration(
+                    USER_ID, requestedChanges, bypassUserPolicyChecks);
+            mTestHandler.waitForMessagesToBeProcessed();
+
+            stateChangeListener.assertNotificationsReceived(1);
+            stateChangeListener.resetNotificationsReceivedCount();
+        }
+    }
+
+    // Perform a (not current user) update via the strategy. There's no listener behavior for
+    // updates to "other" users.
+    @Test
+    public void testChangeListenerBehavior_otherUser() throws Exception {
+        ConfigurationInternal currentUserConfig = CONFIG_AUTO_DISABLED_GEO_DISABLED;
+        // The strategy initializes itself with the current user's config during construction.
+        assertEquals(currentUserConfig,
+                mTimeZoneDetectorStrategy.getCachedCapabilitiesAndConfigForTests());
+
+        TestStateChangeListener stateChangeListener = new TestStateChangeListener();
+        mTimeZoneDetectorStrategy.addChangeListener(stateChangeListener);
+
+        boolean bypassUserPolicyChecks = false;
+
+        int otherUserId = currentUserConfig.getUserId() + 1;
+        ConfigurationInternal otherUserConfig = new ConfigurationInternal.Builder(currentUserConfig)
+                .setUserId(otherUserId)
+                .setGeoDetectionEnabledSetting(true)
+                .build();
+        mFakeServiceConfigAccessorSpy.initializeOtherUserConfiguration(otherUserConfig);
+
+        TimeZoneConfiguration requestedChanges =
+                new TimeZoneConfiguration.Builder().setGeoDetectionEnabled(false).build();
+        mTimeZoneDetectorStrategy.updateConfiguration(
+                otherUserId, requestedChanges, bypassUserPolicyChecks);
+        mTestHandler.waitForMessagesToBeProcessed();
+
+        // Only changes to the current user's config are notified.
+        stateChangeListener.assertNotificationsReceived(0);
+        stateChangeListener.resetNotificationsReceivedCount();
+    }
+
+    // Current user behavior: the strategy caches and returns the latest configuration.
+    @Test
+    public void testReadAndWriteConfiguration_currentUser() throws Exception {
+        ConfigurationInternal currentUserConfig = CONFIG_AUTO_ENABLED_GEO_DISABLED;
+        mFakeServiceConfigAccessorSpy.simulateCurrentUserConfigurationInternalChange(
+                currentUserConfig);
+
+        int otherUserId = currentUserConfig.getUserId() + 1;
+        ConfigurationInternal otherUserConfig = new ConfigurationInternal.Builder(currentUserConfig)
+                .setUserId(otherUserId)
+                .setGeoDetectionEnabledSetting(true)
+                .build();
+        mFakeServiceConfigAccessorSpy.simulateOtherUserConfigurationInternalChange(otherUserConfig);
+        reset(mFakeServiceConfigAccessorSpy);
+
+        final boolean bypassUserPolicyChecks = false;
+
+        ConfigurationInternal cachedConfigurationInternal =
+                mTimeZoneDetectorStrategy.getCachedCapabilitiesAndConfigForTests();
+        assertEquals(currentUserConfig, cachedConfigurationInternal);
+
+        // Confirm getCapabilitiesAndConfig() does not call through to the ServiceConfigAccessor.
+        {
+            reset(mFakeServiceConfigAccessorSpy);
+            TimeZoneCapabilitiesAndConfig actualCapabilitiesAndConfig =
+                    mTimeZoneDetectorStrategy.getCapabilitiesAndConfig(
+                            currentUserConfig.getUserId(), bypassUserPolicyChecks);
+            verify(mFakeServiceConfigAccessorSpy, never()).getConfigurationInternal(
+                    currentUserConfig.getUserId());
+
+            assertEquals(currentUserConfig.asCapabilities(bypassUserPolicyChecks),
+                    actualCapabilitiesAndConfig.getCapabilities());
+            assertEquals(currentUserConfig.asConfiguration(),
+                    actualCapabilitiesAndConfig.getConfiguration());
+        }
+
+        // Confirm updateConfiguration() calls through to the ServiceConfigAccessor and updates
+        // the cached copy.
+        {
+            boolean newGeoDetectionEnabled =
+                    !cachedConfigurationInternal.asConfiguration().isGeoDetectionEnabled();
+            TimeZoneConfiguration requestedChanges = new TimeZoneConfiguration.Builder()
+                    .setGeoDetectionEnabled(newGeoDetectionEnabled)
+                    .build();
+            ConfigurationInternal expectedConfigAfterChange =
+                    new ConfigurationInternal.Builder(cachedConfigurationInternal)
+                            .setGeoDetectionEnabledSetting(newGeoDetectionEnabled)
+                            .build();
+
+            reset(mFakeServiceConfigAccessorSpy);
+            mTimeZoneDetectorStrategy.updateConfiguration(
+                    currentUserConfig.getUserId(), requestedChanges, bypassUserPolicyChecks);
+            verify(mFakeServiceConfigAccessorSpy, times(1)).updateConfiguration(
+                    currentUserConfig.getUserId(), requestedChanges, bypassUserPolicyChecks);
+            assertEquals(expectedConfigAfterChange,
+                    mTimeZoneDetectorStrategy.getCachedCapabilitiesAndConfigForTests());
+        }
+    }
+
+    // Not current user behavior: the strategy reads from the ServiceConfigAccessor.
+    @Test
+    public void testReadAndWriteConfiguration_otherUser() throws Exception {
+        ConfigurationInternal currentUserConfig = CONFIG_AUTO_ENABLED_GEO_DISABLED;
+        mFakeServiceConfigAccessorSpy.simulateCurrentUserConfigurationInternalChange(
+                currentUserConfig);
+
+        int otherUserId = currentUserConfig.getUserId() + 1;
+        ConfigurationInternal otherUserConfig = new ConfigurationInternal.Builder(currentUserConfig)
+                .setUserId(otherUserId)
+                .setGeoDetectionEnabledSetting(true)
+                .build();
+        mFakeServiceConfigAccessorSpy.simulateOtherUserConfigurationInternalChange(otherUserConfig);
+        reset(mFakeServiceConfigAccessorSpy);
+
+        final boolean bypassUserPolicyChecks = false;
+
+        // Confirm getCapabilitiesAndConfig() does not call through to the ServiceConfigAccessor.
+        {
+            reset(mFakeServiceConfigAccessorSpy);
+            TimeZoneCapabilitiesAndConfig actualCapabilitiesAndConfig =
+                    mTimeZoneDetectorStrategy.getCapabilitiesAndConfig(
+                            otherUserId, bypassUserPolicyChecks);
+            verify(mFakeServiceConfigAccessorSpy, times(1)).getConfigurationInternal(otherUserId);
+
+            assertEquals(otherUserConfig.asCapabilities(bypassUserPolicyChecks),
+                    actualCapabilitiesAndConfig.getCapabilities());
+            assertEquals(otherUserConfig.asConfiguration(),
+                    actualCapabilitiesAndConfig.getConfiguration());
+        }
+
+        // Confirm updateConfiguration() calls through to the ServiceConfigAccessor and doesn't
+        // touch the cached copy.
+        {
+            ConfigurationInternal cachedConfigBeforeChange =
+                    mTimeZoneDetectorStrategy.getCachedCapabilitiesAndConfigForTests();
+            boolean newGeoDetectionEnabled =
+                    !otherUserConfig.asConfiguration().isGeoDetectionEnabled();
+            TimeZoneConfiguration requestedChanges = new TimeZoneConfiguration.Builder()
+                    .setGeoDetectionEnabled(newGeoDetectionEnabled)
+                    .build();
+
+            reset(mFakeServiceConfigAccessorSpy);
+            mTimeZoneDetectorStrategy.updateConfiguration(
+                    currentUserConfig.getUserId(), requestedChanges, bypassUserPolicyChecks);
+            verify(mFakeServiceConfigAccessorSpy, times(1)).updateConfiguration(
+                    currentUserConfig.getUserId(), requestedChanges, bypassUserPolicyChecks);
+            assertEquals(cachedConfigBeforeChange,
+                    mTimeZoneDetectorStrategy.getCachedCapabilitiesAndConfigForTests());
+        }
     }
 
     @Test
@@ -1201,19 +1425,13 @@
 
         private final TestState<String> mTimeZoneId = new TestState<>();
         private final TestState<Integer> mTimeZoneConfidence = new TestState<>();
-        private ConfigurationInternal mConfigurationInternal;
         private @ElapsedRealtimeLong long mElapsedRealtimeMillis;
-        private ConfigurationChangeListener mConfigurationInternalChangeListener;
 
         FakeEnvironment() {
             // Ensure the fake environment starts with the defaults a fresh device would.
             initializeTimeZoneSetting("", TIME_ZONE_CONFIDENCE_LOW);
         }
 
-        void initializeConfig(ConfigurationInternal configurationInternal) {
-            mConfigurationInternal = configurationInternal;
-        }
-
         void initializeClock(@ElapsedRealtimeLong long elapsedRealtimeMillis) {
             mElapsedRealtimeMillis = elapsedRealtimeMillis;
         }
@@ -1228,16 +1446,6 @@
         }
 
         @Override
-        public void setConfigurationInternalChangeListener(ConfigurationChangeListener listener) {
-            mConfigurationInternalChangeListener = listener;
-        }
-
-        @Override
-        public ConfigurationInternal getCurrentUserConfigurationInternal() {
-            return mConfigurationInternal;
-        }
-
-        @Override
         public String getDeviceTimeZone() {
             return mTimeZoneId.getLatest();
         }
@@ -1254,11 +1462,6 @@
             mTimeZoneConfidence.set(confidence);
         }
 
-        void simulateConfigurationInternalChange(ConfigurationInternal configurationInternal) {
-            mConfigurationInternal = configurationInternal;
-            mConfigurationInternalChangeListener.onChange();
-        }
-
         void assertTimeZoneNotChanged() {
             mTimeZoneId.assertHasNotBeenSet();
             mTimeZoneConfidence.assertHasNotBeenSet();
@@ -1322,7 +1525,8 @@
          * Simulates the user / user's configuration changing.
          */
         Script simulateConfigurationInternalChange(ConfigurationInternal configurationInternal) {
-            mFakeEnvironment.simulateConfigurationInternalChange(configurationInternal);
+            mFakeServiceConfigAccessorSpy.simulateCurrentUserConfigurationInternalChange(
+                    configurationInternal);
             return this;
         }
 
@@ -1331,7 +1535,7 @@
          */
         Script simulateSetAutoMode(boolean autoDetectionEnabled) {
             ConfigurationInternal newConfig = new ConfigurationInternal.Builder(
-                    mFakeEnvironment.getCurrentUserConfigurationInternal())
+                    mFakeServiceConfigAccessorSpy.getCurrentUserConfigurationInternal())
                     .setAutoDetectionEnabledSetting(autoDetectionEnabled)
                     .build();
             simulateConfigurationInternalChange(newConfig);
@@ -1343,7 +1547,7 @@
          */
         Script simulateSetGeoDetectionEnabled(boolean geoDetectionEnabled) {
             ConfigurationInternal newConfig = new ConfigurationInternal.Builder(
-                    mFakeEnvironment.getCurrentUserConfigurationInternal())
+                    mFakeServiceConfigAccessorSpy.getCurrentUserConfigurationInternal())
                     .setGeoDetectionEnabledSetting(geoDetectionEnabled)
                     .build();
             simulateConfigurationInternalChange(newConfig);
@@ -1457,4 +1661,22 @@
             @MatchType int matchType, @Quality int quality, int expectedScore) {
         return new TelephonyTestCase(matchType, quality, expectedScore);
     }
+
+    private static class TestStateChangeListener implements StateChangeListener {
+
+        private int mNotificationsReceived;
+
+        @Override
+        public void onChange() {
+            mNotificationsReceived++;
+        }
+
+        public void resetNotificationsReceivedCount() {
+            mNotificationsReceived = 0;
+        }
+
+        public void assertNotificationsReceived(int expectedCount) {
+            assertEquals(expectedCount, mNotificationsReceived);
+        }
+    }
 }
diff --git a/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java b/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java
index 2c3a7c4..042e3ef8 100644
--- a/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java
+++ b/services/tests/servicestests/src/com/android/server/timezonedetector/location/TestSupport.java
@@ -42,7 +42,8 @@
 
     private static ConfigurationInternal createUserConfig(
             @UserIdInt int userId, boolean geoDetectionEnabledSetting) {
-        return new ConfigurationInternal.Builder(userId)
+        return new ConfigurationInternal.Builder()
+                .setUserId(userId)
                 .setUserConfigAllowed(true)
                 .setTelephonyDetectionFeatureSupported(true)
                 .setGeoDetectionFeatureSupported(true)
diff --git a/services/tests/servicestests/src/com/android/server/utils/EventLoggerTest.java b/services/tests/servicestests/src/com/android/server/utils/EventLoggerTest.java
index 0b27f87..aafc16d 100644
--- a/services/tests/servicestests/src/com/android/server/utils/EventLoggerTest.java
+++ b/services/tests/servicestests/src/com/android/server/utils/EventLoggerTest.java
@@ -29,8 +29,10 @@
 
 import java.io.PrintWriter;
 import java.io.StringWriter;
+import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.List;
 
 @SmallTest
 @RunWith(Enclosed.class)
@@ -51,17 +53,25 @@
         private StringWriter mTestStringWriter;
         private PrintWriter mTestPrintWriter;
 
+        private TestDumpSink mTestConsumer;
         private EventLogger mEventLogger;
 
         @Before
         public void setUp() {
             mTestStringWriter = new StringWriter();
             mTestPrintWriter = new PrintWriter(mTestStringWriter);
+            mTestConsumer = new TestDumpSink();
             mEventLogger = new EventLogger(EVENTS_LOGGER_SIZE, EVENTS_LOGGER_TAG);
         }
 
         @Test
-        public void testThatConsumeOfEmptyLoggerProducesEmptyList() {
+        public void testThatConsumerProducesEmptyListFromEmptyLog() {
+            mEventLogger.dump(mTestConsumer);
+            assertThat(mTestConsumer.getLastKnownConsumedEvents()).isEmpty();
+        }
+
+        @Test
+        public void testThatPrintWriterProducesEmptyListFromEmptyLog() {
             mEventLogger.dump(mTestPrintWriter);
             assertThat(mTestStringWriter.toString()).isEmpty();
         }
@@ -102,10 +112,12 @@
             });
         }
 
+        private TestDumpSink mTestConsumer;
         private EventLogger mEventLogger;
 
         private final StringWriter mTestStringWriter;
         private final PrintWriter mTestPrintWriter;
+
         private final EventLogger.Event[] mEventsToInsert;
         private final EventLogger.Event[] mExpectedEvents;
 
@@ -119,13 +131,27 @@
 
         @Before
         public void setUp() {
+            mTestConsumer = new TestDumpSink();
             mEventLogger = new EventLogger(EVENTS_LOGGER_SIZE, EVENTS_LOGGER_TAG);
         }
 
         @Test
-        public void testThatLoggingWorksAsExpected() {
+        public void testThatConsumerDumpsEventsAsExpected() {
             for (EventLogger.Event event: mEventsToInsert) {
-                mEventLogger.log(event);
+                mEventLogger.enqueue(event);
+            }
+
+            mEventLogger.dump(mTestConsumer);
+
+            assertThat(mTestConsumer.getLastKnownConsumedEvents())
+                    .containsExactlyElementsIn(mExpectedEvents);
+        }
+
+
+        @Test
+        public void testThatPrintWriterDumpsEventsAsExpected() {
+            for (EventLogger.Event event: mEventsToInsert) {
+                mEventLogger.enqueue(event);
             }
 
             mEventLogger.dump(mTestPrintWriter);
@@ -149,11 +175,27 @@
         return stringWriter.toString();
     }
 
-    private static class TestEvent extends EventLogger.Event {
+
+    private static final class TestEvent extends EventLogger.Event {
 
         @Override
         public String eventToString() {
             return getClass().getName() + "@" + Integer.toHexString(hashCode());
         }
     }
+
+    private static final class TestDumpSink implements EventLogger.DumpSink {
+
+        private final ArrayList<EventLogger.Event> mEvents = new ArrayList<>();
+
+        @Override
+        public void sink(String tag, List<EventLogger.Event> events) {
+            mEvents.clear();
+            mEvents.addAll(events);
+        }
+
+        public ArrayList<EventLogger.Event> getLastKnownConsumedEvents() {
+            return new ArrayList<>(mEvents);
+        }
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/slice/SliceManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/slice/SliceManagerServiceTest.java
index a917c57..6ef5b0e 100644
--- a/services/tests/uiservicestests/src/com/android/server/slice/SliceManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/slice/SliceManagerServiceTest.java
@@ -51,6 +51,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -87,6 +88,7 @@
         LocalServices.removeServiceForTest(UsageStatsManagerInternal.class);
     }
 
+    @Ignore("b/253871109")
     @Test
     public void testAddPinCreatesPinned() throws RemoteException {
         grantSlicePermission();
@@ -97,6 +99,7 @@
         verify(mService, times(1)).createPinnedSlice(eq(maybeAddUserId(TEST_URI, 0)), anyString());
     }
 
+    @Ignore("b/253871109")
     @Test
     public void testRemovePinDestroysPinned() throws RemoteException {
         grantSlicePermission();
@@ -109,6 +112,7 @@
         verify(mCreatedSliceState, never()).destroy();
     }
 
+    @Ignore("b/253871109")
     @Test
     public void testCheckAutoGrantPermissions() throws RemoteException {
         String[] testPerms = new String[] {
@@ -128,12 +132,14 @@
         verify(mContextSpy).checkPermission(eq("perm2"), eq(Process.myPid()), eq(Process.myUid()));
     }
 
+    @Ignore("b/253871109")
     @Test(expected = IllegalStateException.class)
     public void testNoPinThrow() throws Exception {
         grantSlicePermission();
         mService.getPinnedSpecs(TEST_URI, "pkg");
     }
 
+    @Ignore("b/253871109")
     @Test
     public void testGetPinnedSpecs() throws Exception {
         grantSlicePermission();
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 4ec9762..3a8e1cc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -2884,6 +2884,7 @@
         fragmentSetup.accept(taskFragment1, new Rect(0, 0, width / 2, height));
         task.addChild(taskFragment1, POSITION_TOP);
         assertEquals(task, activity1.mStartingData.mAssociatedTask);
+        assertEquals(activity1.mStartingData, task.mSharedStartingData);
 
         final TaskFragment taskFragment2 = new TaskFragment(
                 mAtm, null /* fragmentToken */, false /* createdByOrganizer */);
@@ -2903,7 +2904,6 @@
 
         verify(activity1.getSyncTransaction()).reparent(eq(startingWindow.mSurfaceControl),
                 eq(task.mSurfaceControl));
-        assertEquals(activity1.mStartingData, startingWindow.mStartingData);
         assertEquals(task.mSurfaceControl, startingWindow.getAnimationLeashParent());
         assertEquals(taskFragment1.getBounds(), activity1.getBounds());
         // The activity was resized by task fragment, but starting window must still cover the task.
@@ -2914,6 +2914,7 @@
         activity1.onFirstWindowDrawn(activityWindow);
         activity2.onFirstWindowDrawn(activityWindow);
         assertNull(activity1.mStartingWindow);
+        assertNull(task.mSharedStartingData);
     }
 
     @Test
@@ -2989,10 +2990,10 @@
         final WindowManager.LayoutParams attrs =
                 new WindowManager.LayoutParams(TYPE_APPLICATION_STARTING);
         final TestWindowState startingWindow = createWindowState(attrs, activity);
-        activity.startingDisplayed = true;
+        activity.mStartingData = mock(StartingData.class);
         activity.addWindow(startingWindow);
         assertTrue("Starting window should be present", activity.hasStartingWindow());
-        activity.startingDisplayed = false;
+        activity.mStartingData = null;
         assertTrue("Starting window should be present", activity.hasStartingWindow());
 
         activity.removeChild(startingWindow);
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 2b0e76c..053718e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -98,6 +98,7 @@
 import android.util.Pair;
 import android.util.Size;
 import android.view.Gravity;
+import android.view.RemoteAnimationAdapter;
 import android.window.TaskFragmentOrganizerToken;
 
 import androidx.test.filters.SmallTest;
@@ -1323,6 +1324,32 @@
     }
 
     @Test
+    public void testRemoteAnimation_appliesToExistingTask() {
+        final ActivityStarter starter = prepareStarter(0, false);
+
+        // Put an activity on default display as the top focused activity.
+        ActivityRecord r = new ActivityBuilder(mAtm).setCreateTask(true).build();
+        final Intent intent = new Intent();
+        intent.setComponent(ActivityBuilder.getDefaultComponent());
+        starter.setReason("testRemoteAnimation_newTask")
+                .setIntent(intent)
+                .execute();
+
+        assertNull(mRootWindowContainer.topRunningActivity().mPendingRemoteAnimation);
+
+        // Relaunch the activity with remote animation indicated in options.
+        final RemoteAnimationAdapter adaptor = mock(RemoteAnimationAdapter.class);
+        final ActivityOptions options = ActivityOptions.makeRemoteAnimation(adaptor);
+        starter.setReason("testRemoteAnimation_existingTask")
+                .setIntent(intent)
+                .setActivityOptions(options.toBundle())
+                .execute();
+
+        // Verify the remote animation is updated.
+        assertEquals(adaptor, mRootWindowContainer.topRunningActivity().mPendingRemoteAnimation);
+    }
+
+    @Test
     public void testStartLaunchIntoPipActivity() {
         final ActivityStarter starter = prepareStarter(0, false);
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
index 513791d..0332c4b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionControllerTest.java
@@ -1300,6 +1300,8 @@
         activity.allDrawn = true;
         // Skip manipulate the SurfaceControl.
         doNothing().when(activity).setDropInputMode(anyInt());
+        // Assume the activity contains a window.
+        doReturn(true).when(activity).hasChild();
         // Make sure activity can create remote animation target.
         doReturn(mock(RemoteAnimationTarget.class)).when(activity).createRemoteAnimationTarget(
                 any());
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
index f61effa..d55e53c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppTransitionTests.java
@@ -321,7 +321,6 @@
         final ActivityRecord activity2 = createActivityRecord(dc2);
 
         activity1.allDrawn = true;
-        activity1.startingDisplayed = true;
         activity1.startingMoved = true;
 
         // Simulate activity resume / finish flows to prepare app transition & set visibility,
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index bc319db..e3b389b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -98,12 +98,19 @@
     @Test
     public void backTypeCrossTaskWhenBackToPreviousTask() {
         Task taskA = createTask(mDefaultDisplay);
-        createActivityRecord(taskA);
+        ActivityRecord recordA = createActivityRecord(taskA);
+        Mockito.doNothing().when(recordA).reparentSurfaceControl(any(), any());
+
         withSystemCallback(createTopTaskWithActivity());
         BackNavigationInfo backNavigationInfo = startBackNavigation();
         assertWithMessage("BackNavigationInfo").that(backNavigationInfo).isNotNull();
         assertThat(typeToString(backNavigationInfo.getType()))
                 .isEqualTo(typeToString(BackNavigationInfo.TYPE_CROSS_TASK));
+
+        // verify if back animation would start.
+        verify(mBackNavigationController).scheduleAnimationLocked(
+                eq(BackNavigationInfo.TYPE_CROSS_TASK), any(), eq(mBackAnimationAdapter),
+                any());
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
index ef84a4b..e85b574 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DimmerTests.java
@@ -24,7 +24,6 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_DIMMER;
 
-import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -139,34 +138,12 @@
     }
 
     @Test
-    public void testDimAboveNoChildCreatesSurface() {
-        final float alpha = 0.8f;
-        mDimmer.dimAbove(mTransaction, alpha);
-
-        SurfaceControl dimLayer = getDimLayer();
-
-        assertNotNull("Dimmer should have created a surface", dimLayer);
-
-        verify(mTransaction).setAlpha(dimLayer, alpha);
-        verify(mTransaction).setLayer(dimLayer, Integer.MAX_VALUE);
-    }
-
-    @Test
-    public void testDimAboveNoChildRedundantlyUpdatesAlphaOnExistingSurface() {
-        float alpha = 0.8f;
-        mDimmer.dimAbove(mTransaction, alpha);
-        final SurfaceControl firstSurface = getDimLayer();
-
-        alpha = 0.9f;
-        mDimmer.dimAbove(mTransaction, alpha);
-
-        assertEquals(firstSurface, getDimLayer());
-        verify(mTransaction).setAlpha(firstSurface, 0.9f);
-    }
-
-    @Test
     public void testUpdateDimsAppliesCrop() {
-        mDimmer.dimAbove(mTransaction, 0.8f);
+        TestWindowContainer child = new TestWindowContainer(mWm);
+        mHost.addChild(child, 0);
+
+        final float alpha = 0.8f;
+        mDimmer.dimAbove(mTransaction, child, alpha);
 
         int width = 100;
         int height = 300;
@@ -178,17 +155,6 @@
     }
 
     @Test
-    public void testDimAboveNoChildNotReset() {
-        mDimmer.dimAbove(mTransaction, 0.8f);
-        SurfaceControl dimLayer = getDimLayer();
-        mDimmer.resetDimStates();
-
-        mDimmer.updateDims(mTransaction, new Rect());
-        verify(mTransaction).show(getDimLayer());
-        verify(mTransaction, never()).remove(dimLayer);
-    }
-
-    @Test
     public void testDimAboveWithChildCreatesSurfaceAboveChild() {
         TestWindowContainer child = new TestWindowContainer(mWm);
         mHost.addChild(child, 0);
diff --git a/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java b/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java
index b1acae2..eab2e15 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RunningTasksTest.java
@@ -42,7 +42,6 @@
 import org.junit.runner.RunWith;
 
 import java.util.ArrayList;
-import java.util.List;
 
 /**
  * Build/Install/Run:
@@ -66,55 +65,6 @@
     }
 
     @Test
-    public void testCollectTasksByLastActiveTime() {
-        // Create a number of stacks with tasks (of incrementing active time)
-        final ArrayList<DisplayContent> displays = new ArrayList<>();
-        final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2500).build();
-        displays.add(display);
-
-        final int numStacks = 2;
-        for (int stackIndex = 0; stackIndex < numStacks; stackIndex++) {
-            final Task stack = new TaskBuilder(mSupervisor)
-                    .setDisplay(display)
-                    .setOnTop(false)
-                    .build();
-        }
-
-        final int numTasks = 10;
-        int activeTime = 0;
-        final List<Task> rootTasks = new ArrayList<>();
-        display.getDefaultTaskDisplayArea().forAllRootTasks(task -> {
-            rootTasks.add(task);
-        }, false /* traverseTopToBottom */);
-        for (int i = 0; i < numTasks; i++) {
-            final Task task =
-                    createTask(rootTasks.get(i % numStacks), ".Task" + i, i, activeTime++, null);
-            doReturn(false).when(task).isVisible();
-        }
-
-        // Ensure that the latest tasks were returned in order of decreasing last active time,
-        // collected from all tasks across all the stacks
-        final int numFetchTasks = 5;
-        ArrayList<RunningTaskInfo> tasks = new ArrayList<>();
-        mRunningTasks.getTasks(5, tasks, FLAG_ALLOWED | FLAG_CROSS_USERS,
-                mAtm.getRecentTasks(), mRootWindowContainer, -1 /* callingUid */, PROFILE_IDS);
-        assertThat(tasks).hasSize(numFetchTasks);
-        for (int i = 0; i < numFetchTasks; i++) {
-            assertEquals(numTasks - i - 1, tasks.get(i).id);
-        }
-
-        // Ensure that requesting more than the total number of tasks only returns the subset
-        // and does not crash
-        tasks.clear();
-        mRunningTasks.getTasks(100, tasks, FLAG_ALLOWED | FLAG_CROSS_USERS,
-                mAtm.getRecentTasks(), mRootWindowContainer, -1 /* callingUid */, PROFILE_IDS);
-        assertThat(tasks).hasSize(numTasks);
-        for (int i = 0; i < numTasks; i++) {
-            assertEquals(numTasks - i - 1, tasks.get(i).id);
-        }
-    }
-
-    @Test
     public void testTaskInfo_expectNoExtrasByDefault() {
         final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2500).build();
         final int numTasks = 10;
@@ -125,7 +75,7 @@
                     .build();
             final Bundle data = new Bundle();
             data.putInt("key", 100);
-            createTask(stack, ".Task" + i, i, i, data);
+            createTask(stack, ".Task" + i, i, data);
         }
 
         final int numFetchTasks = 5;
@@ -150,7 +100,7 @@
                     .build();
             final Bundle data = new Bundle();
             data.putInt("key", 100);
-            createTask(stack, ".Task" + i, i, i, data);
+            createTask(stack, ".Task" + i, i, data);
         }
 
         final int numFetchTasks = 5;
@@ -167,46 +117,63 @@
     }
 
     @Test
-    public void testUpdateLastActiveTimeOfVisibleTasks() {
+    public void testGetTasksSortByFocusAndVisibility() {
         final DisplayContent display = new TestDisplayContent.Builder(mAtm, 1000, 2500).build();
+        final Task stack = new TaskBuilder(mSupervisor)
+                .setDisplay(display)
+                .setOnTop(true)
+                .build();
+
         final int numTasks = 10;
         final ArrayList<Task> tasks = new ArrayList<>();
         for (int i = 0; i < numTasks; i++) {
-            final Task task = createTask(null, ".Task" + i, i, i, null);
+            final Task task = createTask(stack, ".Task" + i, i, null);
             doReturn(false).when(task).isVisible();
             tasks.add(task);
         }
 
-        final Task visibleTask = tasks.get(0);
-        doReturn(true).when(visibleTask).isVisible();
-
-        final Task focusedTask = tasks.get(1);
+        final Task focusedTask = tasks.get(numTasks - 1);
         doReturn(true).when(focusedTask).isVisible();
-        doReturn(true).when(focusedTask).isFocused();
+        display.mFocusedApp = focusedTask.getTopNonFinishingActivity();
 
-        // Ensure that the last active time of visible tasks were updated while the focused one had
-        // the largest last active time.
+        final Task visibleTaskTop = tasks.get(numTasks - 2);
+        doReturn(true).when(visibleTaskTop).isVisible();
+
+        final Task visibleTaskBottom = tasks.get(numTasks - 3);
+        doReturn(true).when(visibleTaskBottom).isVisible();
+
+        // Ensure that the focused Task is on top, visible tasks below, then invisible tasks.
         final int numFetchTasks = 5;
         final ArrayList<RunningTaskInfo> fetchTasks = new ArrayList<>();
         mRunningTasks.getTasks(numFetchTasks, fetchTasks,
                 FLAG_ALLOWED | FLAG_CROSS_USERS | FLAG_KEEP_INTENT_EXTRA,
                 mAtm.getRecentTasks(), mRootWindowContainer, -1 /* callingUid */, PROFILE_IDS);
         assertThat(fetchTasks).hasSize(numFetchTasks);
-        assertEquals(fetchTasks.get(0).id, focusedTask.mTaskId);
-        assertEquals(fetchTasks.get(1).id, visibleTask.mTaskId);
+        for (int i = 0; i < numFetchTasks; i++) {
+            assertEquals(numTasks - i - 1, fetchTasks.get(i).id);
+        }
+
+        // Ensure that requesting more than the total number of tasks only returns the subset
+        // and does not crash
+        fetchTasks.clear();
+        mRunningTasks.getTasks(100, fetchTasks,
+                FLAG_ALLOWED | FLAG_CROSS_USERS | FLAG_KEEP_INTENT_EXTRA,
+                mAtm.getRecentTasks(), mRootWindowContainer, -1 /* callingUid */, PROFILE_IDS);
+        assertThat(fetchTasks).hasSize(numTasks);
+        for (int i = 0; i < numTasks; i++) {
+            assertEquals(numTasks - i - 1, fetchTasks.get(i).id);
+        }
     }
 
     /**
-     * Create a task with a single activity in it, with the given last active time.
+     * Create a task with a single activity in it.
      */
-    private Task createTask(Task stack, String className, int taskId,
-            int lastActiveTime, Bundle extras) {
+    private Task createTask(Task stack, String className, int taskId, Bundle extras) {
         final Task task = new TaskBuilder(mAtm.mTaskSupervisor)
                 .setComponent(new ComponentName(mContext.getPackageName(), className))
                 .setTaskId(taskId)
                 .setParentTaskFragment(stack)
                 .build();
-        task.lastActiveTime = lastActiveTime;
         final ActivityRecord activity = new ActivityBuilder(mAtm)
                 .setTask(task)
                 .setComponent(new ComponentName(mContext.getPackageName(), ".TaskActivity"))
@@ -227,7 +194,7 @@
                     .setDisplay(i % 2 == 0 ? display0 : display1)
                     .setOnTop(true)
                     .build();
-            final Task task = createTask(stack, ".Task" + i, i, i, null);
+            final Task task = createTask(stack, ".Task" + i, i, null);
             tasks.add(task);
         }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index d59fce0..c906abc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -87,7 +87,6 @@
 import android.provider.DeviceConfig;
 import android.provider.DeviceConfig.Properties;
 import android.view.InsetsFrameProvider;
-import android.view.InsetsVisibilities;
 import android.view.WindowManager;
 
 import androidx.test.filters.MediumTest;
@@ -2302,8 +2301,7 @@
         // We should get a null LetterboxDetails object as there is no letterboxed activity, so
         // nothing will get passed to SysUI
         verify(statusBar, never()).onSystemBarAttributesChanged(anyInt(), anyInt(),
-                any(), anyBoolean(), anyInt(),
-                any(InsetsVisibilities.class), isNull(), isNull());
+                any(), anyBoolean(), anyInt(), anyInt(), isNull(), isNull());
 
     }
 
@@ -2331,8 +2329,7 @@
         // Check that letterboxDetails actually gets passed to SysUI
         StatusBarManagerInternal statusBar = displayPolicy.getStatusBarManagerInternal();
         verify(statusBar).onSystemBarAttributesChanged(anyInt(), anyInt(),
-                any(), anyBoolean(), anyInt(),
-                any(InsetsVisibilities.class), isNull(), eq(expectedLetterboxDetails));
+                any(), anyBoolean(), anyInt(), anyInt(), isNull(), eq(expectedLetterboxDetails));
     }
 
     @Test
@@ -2367,8 +2364,7 @@
         // Check that letterboxDetails actually gets passed to SysUI
         StatusBarManagerInternal statusBarManager = displayPolicy.getStatusBarManagerInternal();
         verify(statusBarManager).onSystemBarAttributesChanged(anyInt(), anyInt(),
-                any(), anyBoolean(), anyInt(),
-                any(InsetsVisibilities.class), isNull(), eq(expectedLetterboxDetails));
+                any(), anyBoolean(), anyInt(), anyInt(), isNull(), eq(expectedLetterboxDetails));
     }
 
     @Test
@@ -2420,8 +2416,7 @@
         // Check that letterboxDetails actually gets passed to SysUI
         StatusBarManagerInternal statusBar = displayPolicy.getStatusBarManagerInternal();
         verify(statusBar).onSystemBarAttributesChanged(anyInt(), anyInt(),
-                any(), anyBoolean(), anyInt(),
-                any(InsetsVisibilities.class), isNull(), eq(expectedLetterboxDetails));
+                any(), anyBoolean(), anyInt(), anyInt(), isNull(), eq(expectedLetterboxDetails));
     }
 
     private void recomputeNaturalConfigurationOfUnresizableActivity() {
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index d4c9087..35b9710 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -1520,6 +1520,29 @@
         transition.abort();
     }
 
+    @Test
+    public void testCollectReparentChange() {
+        registerTestTransitionPlayer();
+
+        // Reparent activity in transition.
+        final Task lastParent = createTask(mDisplayContent);
+        final Task newParent = createTask(mDisplayContent);
+        final ActivityRecord activity = createActivityRecord(lastParent);
+        doReturn(true).when(lastParent).shouldRemoveSelfOnLastChildRemoval();
+        doNothing().when(activity).setDropInputMode(anyInt());
+        activity.mVisibleRequested = true;
+
+        final Transition transition = new Transition(TRANSIT_CHANGE, 0 /* flags */,
+                activity.mTransitionController, mWm.mSyncEngine);
+        activity.mTransitionController.moveToCollecting(transition);
+        transition.collect(activity);
+        activity.reparent(newParent, POSITION_TOP);
+
+        // ChangeInfo#mCommonAncestor should be set after reparent.
+        final Transition.ChangeInfo change = transition.mChanges.get(activity);
+        assertEquals(newParent.getDisplayArea(), change.mCommonAncestor);
+    }
+
     private static void makeTaskOrganized(Task... tasks) {
         final ITaskOrganizer organizer = mock(ITaskOrganizer.class);
         for (Task t : tasks) {
diff --git a/services/usb/java/com/android/server/usb/DualOutputStreamDumpSink.java b/services/usb/java/com/android/server/usb/DualOutputStreamDumpSink.java
new file mode 100644
index 0000000..cea3d87
--- /dev/null
+++ b/services/usb/java/com/android/server/usb/DualOutputStreamDumpSink.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.usb;
+
+import com.android.internal.util.dump.DualDumpOutputStream;
+import com.android.server.utils.EventLogger;
+
+import java.util.List;
+
+/**
+ * Writes logs to {@link DualDumpOutputStream}.
+ *
+ * @see EventLogger.DumpSink
+ * @see DualDumpOutputStream
+ */
+final class DualOutputStreamDumpSink implements EventLogger.DumpSink {
+
+    private final long mId;
+    private final DualDumpOutputStream mDumpOutputStream;
+
+    /* package */ DualOutputStreamDumpSink(DualDumpOutputStream dualDumpOutputStream, long id) {
+        mDumpOutputStream = dualDumpOutputStream;
+        mId = id;
+    }
+
+    /**
+     * {@inheritDoc}
+     */
+    @Override
+    public void sink(String tag, List<EventLogger.Event> events) {
+        mDumpOutputStream.write("USB Event Log", mId, tag);
+        for (EventLogger.Event evt: events) {
+            mDumpOutputStream.write("USB Event", mId, evt.toString());
+        }
+    }
+}
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceLogger.java b/services/usb/java/com/android/server/usb/UsbDeviceLogger.java
deleted file mode 100644
index fab00bc..0000000
--- a/services/usb/java/com/android/server/usb/UsbDeviceLogger.java
+++ /dev/null
@@ -1,143 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.server.usb;
-
-import android.util.Log;
-
-import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.dump.DualDumpOutputStream;
-
-import java.text.SimpleDateFormat;
-import java.util.Calendar;
-import java.util.LinkedList;
-
-/**
-* Constructor UsbDeviceLogger class
-*/
-public class UsbDeviceLogger {
-    private final Object mLock = new Object();
-
-    // ring buffer of events to log.
-    @GuardedBy("mLock")
-    private final LinkedList<Event> mEvents;
-
-    private final String mTitle;
-
-    // the maximum number of events to keep in log
-    private final int mMemSize;
-
-    /**
-     * Constructor for Event class.
-     */
-    public abstract static class Event {
-        // formatter for timestamps
-        private static final SimpleDateFormat sFormat = new SimpleDateFormat("MM-dd HH:mm:ss:SSS");
-
-        private final Calendar mCalendar;
-
-        Event() {
-            mCalendar = Calendar.getInstance();
-        }
-
-    /**
-     * Convert event to String
-     * @return StringBuilder
-     */
-        public String toString() {
-            return (new StringBuilder(String.format("%tm-%td %tH:%tM:%tS.%tL",
-                    mCalendar, mCalendar, mCalendar, mCalendar, mCalendar, mCalendar)))
-                    .append(" ").append(eventToString()).toString();
-        }
-
-        /**
-         * Causes the string message for the event to appear in the logcat.
-         * Here is an example of how to create a new event (a StringEvent), adding it to the logger
-         * (an instance of UsbDeviceLoggerr) while also making it show in the logcat:
-         * <pre>
-         *     myLogger.log(
-         *         (new StringEvent("something for logcat and logger")).printLog(MyClass.TAG) );
-         * </pre>
-         * @param tag the tag for the android.util.Log.v
-         * @return the same instance of the event
-         */
-        public Event printLog(String tag) {
-            Log.i(tag, eventToString());
-            return this;
-        }
-
-        /**
-         * Convert event to String.
-         * This method is only called when the logger history is about to the dumped,
-         * so this method is where expensive String conversions should be made, not when the Event
-         * subclass is created.
-         * Timestamp information will be automatically added, do not include it.
-         * @return a string representation of the event that occurred.
-         */
-        public abstract String eventToString();
-    }
-
-    /**
-    * Constructor StringEvent class
-    */
-    public static class StringEvent extends Event {
-        private final String mMsg;
-
-        public StringEvent(String msg) {
-            mMsg = msg;
-        }
-
-        @Override
-        public String eventToString() {
-            return mMsg;
-        }
-    }
-
-    /**
-     * Constructor for logger.
-     * @param size the maximum number of events to keep in log
-     * @param title the string displayed before the recorded log
-     */
-    public UsbDeviceLogger(int size, String title) {
-        mEvents = new LinkedList<Event>();
-        mMemSize = size;
-        mTitle = title;
-    }
-
-    /**
-     * Constructor for logger.
-     * @param evt the maximum number of events to keep in log
-     */
-    public synchronized void log(Event evt) {
-        synchronized (mLock) {
-            if (mEvents.size() >= mMemSize) {
-                mEvents.removeFirst();
-            }
-            mEvents.add(evt);
-        }
-    }
-
-    /**
-     * Constructor for logger.
-     * @param dump the maximum number of events to keep in log
-     * @param id the category of events
-     */
-    public synchronized void dump(DualDumpOutputStream dump, long id) {
-        dump.write("USB Event Log", id, mTitle);
-        for (Event evt : mEvents) {
-            dump.write("USB Event", id, evt.toString());
-        }
-    }
-}
diff --git a/services/usb/java/com/android/server/usb/UsbDeviceManager.java b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
index e90a376..1c081c1 100644
--- a/services/usb/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/usb/java/com/android/server/usb/UsbDeviceManager.java
@@ -91,6 +91,7 @@
 import com.android.internal.util.dump.DualDumpOutputStream;
 import com.android.server.FgThread;
 import com.android.server.LocalServices;
+import com.android.server.utils.EventLogger;
 import com.android.server.wm.ActivityTaskManagerInternal;
 
 import java.io.File;
@@ -213,7 +214,7 @@
     private static Set<Integer> sDenyInterfaces;
     private HashMap<Long, FileDescriptor> mControlFds;
 
-    private static UsbDeviceLogger sEventLogger;
+    private static EventLogger sEventLogger;
 
     static {
         sDenyInterfaces = new HashSet<>();
@@ -238,7 +239,7 @@
         public void onUEvent(UEventObserver.UEvent event) {
             if (DEBUG) Slog.v(TAG, "USB UEVENT: " + event.toString());
             if (sEventLogger != null) {
-                sEventLogger.log(new UsbDeviceLogger.StringEvent("USB UEVENT: "
+                sEventLogger.enqueue(new EventLogger.StringEvent("USB UEVENT: "
                         + event.toString()));
             } else {
                 if (DEBUG) Slog.d(TAG, "sEventLogger == null");
@@ -395,7 +396,7 @@
         mUEventObserver.startObserving(USB_STATE_MATCH);
         mUEventObserver.startObserving(ACCESSORY_START_MATCH);
 
-        sEventLogger = new UsbDeviceLogger(DUMPSYS_LOG_BUFFER, "UsbDeviceManager activity");
+        sEventLogger = new EventLogger(DUMPSYS_LOG_BUFFER, "UsbDeviceManager activity");
     }
 
     UsbProfileGroupSettingsManager getCurrentSettings() {
@@ -837,7 +838,7 @@
 
         protected void sendStickyBroadcast(Intent intent) {
             mContext.sendStickyBroadcastAsUser(intent, UserHandle.ALL);
-            sEventLogger.log(new UsbDeviceLogger.StringEvent("USB intent: " + intent));
+            sEventLogger.enqueue(new EventLogger.StringEvent("USB intent: " + intent));
         }
 
         private void updateUsbFunctions() {
@@ -2350,7 +2351,7 @@
 
         if (mHandler != null) {
             mHandler.dump(dump, "handler", UsbDeviceManagerProto.HANDLER);
-            sEventLogger.dump(dump, UsbHandlerProto.UEVENT);
+            sEventLogger.dump(new DualOutputStreamDumpSink(dump, UsbHandlerProto.UEVENT));
         }
 
         dump.end(token);
diff --git a/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java b/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
index 47b09fe..f916660 100644
--- a/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
+++ b/services/usb/java/com/android/server/usb/UsbProfileGroupSettingsManager.java
@@ -62,6 +62,7 @@
 import com.android.internal.util.dump.DualDumpOutputStream;
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
+import com.android.server.utils.EventLogger;
 
 import libcore.io.IoUtils;
 
@@ -130,7 +131,7 @@
     @GuardedBy("mLock")
     private boolean mIsWriteSettingsScheduled;
 
-    private static UsbDeviceLogger sEventLogger;
+    private static EventLogger sEventLogger;
 
     /**
      * A package of a user.
@@ -263,7 +264,7 @@
 
         mUsbHandlerManager = usbResolveActivityManager;
 
-        sEventLogger = new UsbDeviceLogger(DUMPSYS_LOG_BUFFER,
+        sEventLogger = new EventLogger(DUMPSYS_LOG_BUFFER,
                 "UsbProfileGroupSettingsManager activity");
     }
 
@@ -970,7 +971,7 @@
                     matches, mAccessoryPreferenceMap.get(new AccessoryFilter(accessory)));
         }
 
-        sEventLogger.log(new UsbDeviceLogger.StringEvent("accessoryAttached: " + intent));
+        sEventLogger.enqueue(new EventLogger.StringEvent("accessoryAttached: " + intent));
         resolveActivity(intent, matches, defaultActivity, null, accessory);
     }
 
@@ -1524,7 +1525,8 @@
             }
         }
 
-        sEventLogger.dump(dump, UsbProfileGroupSettingsManagerProto.INTENT);
+        sEventLogger.dump(new DualOutputStreamDumpSink(dump,
+                UsbProfileGroupSettingsManagerProto.INTENT));
         dump.end(token);
     }
 
diff --git a/services/voiceinteraction/TEST_MAPPING b/services/voiceinteraction/TEST_MAPPING
index c083e90..af67637 100644
--- a/services/voiceinteraction/TEST_MAPPING
+++ b/services/voiceinteraction/TEST_MAPPING
@@ -23,6 +23,14 @@
           "exclude-annotation": "androidx.test.filters.FlakyTest"
         }
       ]
+    },
+    {
+      "name": "CtsLocalVoiceInteraction",
+      "options": [
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
     }
   ]
 }
diff --git a/telephony/java/android/service/euicc/EuiccService.java b/telephony/java/android/service/euicc/EuiccService.java
index dc695d6..e19117b 100644
--- a/telephony/java/android/service/euicc/EuiccService.java
+++ b/telephony/java/android/service/euicc/EuiccService.java
@@ -730,6 +730,25 @@
     }
 
     /**
+     * Result code to string
+     *
+     * @param result The result code.
+     * @return The result code in string format.
+     *
+     * @hide
+     */
+    public static String resultToString(@Result int result) {
+        switch (result) {
+            case RESULT_OK: return "OK";
+            case RESULT_MUST_DEACTIVATE_SIM : return "MUST_DEACTIVATE_SIM";
+            case RESULT_RESOLVABLE_ERRORS: return "RESOLVABLE_ERRORS";
+            case RESULT_FIRST_USER: return "FIRST_USER";
+            default:
+            return "UNKNOWN(" + result + ")";
+        }
+    }
+
+    /**
      * Wrapper around IEuiccService that forwards calls to implementations of {@link EuiccService}.
      */
     private class IEuiccServiceWrapper extends IEuiccService.Stub {
diff --git a/telephony/java/android/service/euicc/GetEuiccProfileInfoListResult.java b/telephony/java/android/service/euicc/GetEuiccProfileInfoListResult.java
index 9add38e..46a049c 100644
--- a/telephony/java/android/service/euicc/GetEuiccProfileInfoListResult.java
+++ b/telephony/java/android/service/euicc/GetEuiccProfileInfoListResult.java
@@ -123,4 +123,16 @@
     public int describeContents() {
         return 0;
     }
+
+    /**
+     * @hide
+     *
+     * @return String representation of {@link GetEuiccProfileInfoListResult}
+     */
+    @Override
+    public String toString() {
+        return "[GetEuiccProfileInfoListResult: result=" + EuiccService.resultToString(result)
+                + ", isRemovable=" + mIsRemovable + ", mProfiles=" + Arrays.toString(mProfiles)
+                + "]";
+    }
 }
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index d314a65..7be40b8 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -1147,8 +1147,12 @@
             "carrier_data_call_apn_retry_after_disconnect_long";
 
     /**
-     * Data call setup permanent failure causes by the carrier
+     * Data call setup permanent failure causes by the carrier.
+     *
+     * @deprecated This API key was added in mistake and is not used anymore by the telephony data
+     * frameworks.
      */
+    @Deprecated
     public static final String KEY_CARRIER_DATA_CALL_PERMANENT_FAILURE_STRINGS =
             "carrier_data_call_permanent_failure_strings";
 
@@ -2103,6 +2107,16 @@
      * is immediately closed (disabling keep-alive).
      */
     public static final String KEY_MMS_CLOSE_CONNECTION_BOOL = "mmsCloseConnection";
+    /**
+     * Waiting time in milliseconds used before releasing an MMS data call. Not tearing down an MMS
+     * data connection immediately helps to reduce the message delivering latency if messaging
+     * continues between all parties in the conversation since the same data connection can be
+     * reused for further messages.
+     *
+     * This timer will control how long the data call will be kept alive before being torn down.
+     */
+    public static final String KEY_MMS_NETWORK_RELEASE_TIMEOUT_MILLIS_INT =
+            "mms_network_release_timeout_millis_int";
 
     /**
      * The flatten {@link android.content.ComponentName componentName} of the activity that can
@@ -8436,7 +8450,8 @@
      *
      * The syntax of the retry rule:
      * 1. Retry based on {@link NetworkCapabilities}. Note that only APN-type network capabilities
-     *    are supported.
+     *    are supported. If the capabilities are not specified, then the retry rule only applies
+     *    to the current failed APN used in setup data call request.
      * "capabilities=[netCaps1|netCaps2|...], [retry_interval=n1|n2|n3|n4...], [maximum_retries=n]"
      *
      * 2. Retry based on {@link DataFailCause}
@@ -8447,15 +8462,16 @@
      * "capabilities=[netCaps1|netCaps2|...], fail_causes=[cause1|cause2|cause3|...],
      *     [retry_interval=n1|n2|n3|n4...], [maximum_retries=n]"
      *
+     * 4. Permanent fail causes (no timer-based retry) on the current failed APN. Retry interval
+     *    is specified for retrying the next available APN.
+     * "permanent_fail_causes=8|27|28|29|30|32|33|35|50|51|111|-5|-6|65537|65538|-3|65543|65547|
+     *     2252|2253|2254, retry_interval=2500"
+     *
      * For example,
      * "capabilities=eims, retry_interval=1000, maximum_retries=20" means if the attached
      * network request is emergency, then retry data network setup every 1 second for up to 20
      * times.
      *
-     * "fail_causes=8|27|28|29|30|32|33|35|50|51|111|-5|-6|65537|65538|-3|2253|2254
-     * , maximum_retries=0" means for those fail causes, never retry with timers. Note that
-     * when environment changes, retry can still happen.
-     *
      * "capabilities=internet|enterprise|dun|ims|fota, retry_interval=2500|3000|"
      * "5000|10000|15000|20000|40000|60000|120000|240000|600000|1200000|1800000"
      * "1800000, maximum_retries=20" means for those capabilities, retry happens in 2.5s, 3s, 5s,
@@ -8758,6 +8774,22 @@
             "premium_capability_purchase_condition_backoff_hysteresis_time_millis_long";
 
     /**
+     * The amount of time in milliseconds within which the network must set up a slicing
+     * configuration for the premium capability after
+     * {@link TelephonyManager#purchasePremiumCapability(int, Executor, Consumer)}
+     * returns {@link TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_SUCCESS}.
+     * During the setup time, calls to
+     * {@link TelephonyManager#purchasePremiumCapability(int, Executor, Consumer)} will return
+     * {@link TelephonyManager#PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP}.
+     * If the network fails set up a slicing configuration for the premium capability within the
+     * setup time, subsequent purchase requests will be allowed to go through again.
+     *
+     * The default value is 5 minutes.
+     */
+    public static final String KEY_PREMIUM_CAPABILITY_NETWORK_SETUP_TIME_MILLIS_LONG =
+            "premium_capability_network_setup_time_millis_long";
+
+    /**
      * The URL to redirect to when the user clicks on the notification for a network boost via
      * premium capabilities after applications call
      * {@link TelephonyManager#purchasePremiumCapability(int, Executor, Consumer)}.
@@ -9069,6 +9101,7 @@
         sDefaults.putInt(KEY_MMS_SMS_TO_MMS_TEXT_LENGTH_THRESHOLD_INT, -1);
         sDefaults.putInt(KEY_MMS_SMS_TO_MMS_TEXT_THRESHOLD_INT, -1);
         sDefaults.putInt(KEY_MMS_SUBJECT_MAX_LENGTH_INT, 40);
+        sDefaults.putInt(KEY_MMS_NETWORK_RELEASE_TIMEOUT_MILLIS_INT, 5 * 1000);
         sDefaults.putString(KEY_MMS_EMAIL_GATEWAY_NUMBER_STRING, "");
         sDefaults.putString(KEY_MMS_HTTP_PARAMS_STRING, "");
         sDefaults.putString(KEY_MMS_NAI_SUFFIX_STRING, "");
@@ -9432,8 +9465,13 @@
         sDefaults.putStringArray(
                 KEY_TELEPHONY_DATA_SETUP_RETRY_RULES_STRING_ARRAY, new String[] {
                         "capabilities=eims, retry_interval=1000, maximum_retries=20",
-                        "fail_causes=8|27|28|29|30|32|33|35|50|51|111|-5|-6|65537|65538|-3|2252|"
-                                + "2253|2254, maximum_retries=0", // No retry for those causes
+                        // Permanent fail causes. When setup data call fails with the following
+                        // fail causes, telephony data frameworks will stop timer-based retry on
+                        // the failed APN until power cycle, APM, or some special events. Note that
+                        // even timer-based retry is not performed, condition-based (RAT changes,
+                        // registration state changes) retry can still happen.
+                        "permanent_fail_causes=8|27|28|29|30|32|33|35|50|51|111|-5|-6|65537|65538|"
+                                + "-3|65543|65547|2252|2253|2254, retry_interval=2500",
                         "capabilities=mms|supl|cbs, retry_interval=2000",
                         "capabilities=internet|enterprise|dun|ims|fota, retry_interval=2500|3000|"
                                 + "5000|10000|15000|20000|40000|60000|120000|240000|"
@@ -9474,6 +9512,8 @@
         sDefaults.putLong(
                 KEY_PREMIUM_CAPABILITY_PURCHASE_CONDITION_BACKOFF_HYSTERESIS_TIME_MILLIS_LONG,
                 TimeUnit.MINUTES.toMillis(30));
+        sDefaults.putLong(KEY_PREMIUM_CAPABILITY_NETWORK_SETUP_TIME_MILLIS_LONG,
+                TimeUnit.MINUTES.toMillis(5));
         sDefaults.putString(KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING, null);
         sDefaults.putBoolean(KEY_PREMIUM_CAPABILITY_SUPPORTED_ON_LTE_BOOL, false);
         sDefaults.putStringArray(KEY_IWLAN_HANDOVER_POLICY_STRING_ARRAY, new String[]{
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl b/telephony/java/android/telephony/DomainSelectionService.aidl
similarity index 75%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
copy to telephony/java/android/telephony/DomainSelectionService.aidl
index 1550ab3..b9d2ba8 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
+++ b/telephony/java/android/telephony/DomainSelectionService.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2022 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.
@@ -11,9 +11,9 @@
  * 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.
+ * limitations under the License
  */
 
-package com.android.systemui.shared.system;
+package android.telephony;
 
-parcelable RemoteTransitionCompat;
+parcelable DomainSelectionService.SelectionAttributes;
diff --git a/telephony/java/android/telephony/DomainSelectionService.java b/telephony/java/android/telephony/DomainSelectionService.java
new file mode 100644
index 0000000..383561a
--- /dev/null
+++ b/telephony/java/android/telephony/DomainSelectionService.java
@@ -0,0 +1,857 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.SuppressLint;
+import android.app.Service;
+import android.content.Intent;
+import android.os.Build;
+import android.os.CancellationSignal;
+import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.telephony.AccessNetworkConstants.RadioAccessNetworkType;
+import android.telephony.Annotation.DisconnectCauses;
+import android.telephony.Annotation.PreciseDisconnectCauses;
+import android.telephony.ims.ImsReasonInfo;
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.telephony.IDomainSelectionServiceController;
+import com.android.internal.telephony.IDomainSelector;
+import com.android.internal.telephony.ITransportSelectorCallback;
+import com.android.internal.telephony.ITransportSelectorResultCallback;
+import com.android.internal.telephony.IWwanSelectorCallback;
+import com.android.internal.telephony.IWwanSelectorResultCallback;
+import com.android.internal.telephony.util.TelephonyUtils;
+import com.android.telephony.Rlog;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
+import java.util.List;
+import java.util.Objects;
+import java.util.concurrent.CancellationException;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionException;
+import java.util.concurrent.Executor;
+import java.util.function.Consumer;
+
+/**
+ * Main domain selection implementation for various telephony features.
+ *
+ * The telephony framework will bind to the {@link DomainSelectionService}.
+ *
+ * @hide
+ */
+public class DomainSelectionService extends Service {
+
+    private static final String LOG_TAG = "DomainSelectionService";
+
+    /**
+     * The intent that must be defined as an intent-filter in the AndroidManifest of the
+     * {@link DomainSelectionService}.
+     *
+     * @hide
+     */
+    public static final String SERVICE_INTERFACE = "android.telephony.DomainSelectionService";
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "SELECTOR_TYPE_",
+            value = {
+                    SELECTOR_TYPE_CALLING,
+                    SELECTOR_TYPE_SMS,
+                    SELECTOR_TYPE_UT})
+    public @interface SelectorType {}
+
+    /** Indicates the domain selector type for calling. */
+    public static final int SELECTOR_TYPE_CALLING = 1;
+    /** Indicates the domain selector type for sms. */
+    public static final int SELECTOR_TYPE_SMS = 2;
+    /** Indicates the domain selector type for supplementary services. */
+    public static final int SELECTOR_TYPE_UT = 3;
+
+    /** Indicates that the modem can scan for emergency service as per modem’s implementation. */
+    public static final int SCAN_TYPE_NO_PREFERENCE = 0;
+
+    /** Indicates that the modem will scan for emergency service in limited service mode. */
+    public static final int SCAN_TYPE_LIMITED_SERVICE = 1;
+
+    /** Indicates that the modem will scan for emergency service in full service mode. */
+    public static final int SCAN_TYPE_FULL_SERVICE = 2;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = "SCAN_TYPE_",
+            value = {
+                    SCAN_TYPE_NO_PREFERENCE,
+                    SCAN_TYPE_LIMITED_SERVICE,
+                    SCAN_TYPE_FULL_SERVICE})
+    public @interface EmergencyScanType {}
+
+    /**
+     * Contains attributes required to determine the domain for a telephony service.
+     */
+    public static final class SelectionAttributes implements Parcelable {
+
+        private static final String TAG = "SelectionAttributes";
+
+        private int mSlotId;
+        private int mSubId;
+        private @Nullable String mCallId;
+        private @Nullable String mNumber;
+        private @SelectorType int mSelectorType;
+        private boolean mIsVideoCall;
+        private boolean mIsEmergency;
+        private boolean mIsExitedFromAirplaneMode;
+        //private @Nullable UtAttributes mUtAttributes;
+        private @Nullable ImsReasonInfo mImsReasonInfo;
+        private @PreciseDisconnectCauses int mCause;
+        private @Nullable EmergencyRegResult mEmergencyRegResult;
+
+        /**
+         * @param slotId The slot identifier.
+         * @param subId The subscription identifier.
+         * @param callId The call identifier.
+         * @param number The dialed number.
+         * @param selectorType Indicates the requested domain selector type.
+         * @param video Indicates it's a video call.
+         * @param emergency Indicates it's emergency service.
+         * @param exited {@code true} if the request caused the device to move out of airplane mode.
+         * @param imsReasonInfo The reason why the last PS attempt failed.
+         * @param cause The reason why the last CS attempt failed.
+         * @param regResult The current registration result for emergency services.
+         */
+        private SelectionAttributes(int slotId, int subId, @Nullable String callId,
+                @Nullable String number, @SelectorType int selectorType,
+                boolean video, boolean emergency, boolean exited,
+                /*UtAttributes attr,*/
+                @Nullable ImsReasonInfo imsReasonInfo, @PreciseDisconnectCauses int cause,
+                @Nullable EmergencyRegResult regResult) {
+            mSlotId = slotId;
+            mSubId = subId;
+            mCallId = callId;
+            mNumber = number;
+            mSelectorType = selectorType;
+            mIsVideoCall = video;
+            mIsEmergency = emergency;
+            mIsExitedFromAirplaneMode = exited;
+            //mUtAttributes = attr;
+            mImsReasonInfo = imsReasonInfo;
+            mCause = cause;
+            mEmergencyRegResult = regResult;
+        }
+
+        /**
+         * Copy constructor.
+         *
+         * @param s Source selection attributes.
+         * @hide
+         */
+        public SelectionAttributes(@NonNull SelectionAttributes s) {
+            mSlotId = s.mSlotId;
+            mSubId = s.mSubId;
+            mCallId = s.mCallId;
+            mNumber = s.mNumber;
+            mSelectorType = s.mSelectorType;
+            mIsEmergency = s.mIsEmergency;
+            mIsExitedFromAirplaneMode = s.mIsExitedFromAirplaneMode;
+            //mUtAttributes = s.mUtAttributes;
+            mImsReasonInfo = s.mImsReasonInfo;
+            mCause = s.mCause;
+            mEmergencyRegResult = s.mEmergencyRegResult;
+        }
+
+        /**
+         * Constructs a SelectionAttributes object from the given parcel.
+         */
+        private SelectionAttributes(@NonNull Parcel in) {
+            readFromParcel(in);
+        }
+
+        /**
+         * @return The slot identifier.
+         */
+        public int getSlotId() {
+            return mSlotId;
+        }
+
+        /**
+         * @return The subscription identifier.
+         */
+        public int getSubId() {
+            return mSubId;
+        }
+
+        /**
+         * @return The call identifier.
+         */
+        public @Nullable String getCallId() {
+            return mCallId;
+        }
+
+        /**
+         * @return The dialed number.
+         */
+        public @Nullable String getNumber() {
+            return mNumber;
+        }
+
+        /**
+         * @return The domain selector type.
+         */
+        public @SelectorType int getSelectorType() {
+            return mSelectorType;
+        }
+
+        /**
+         * @return {@code true} if the request is for a video call.
+         */
+        public boolean isVideoCall() {
+            return mIsVideoCall;
+        }
+
+        /**
+         * @return {@code true} if the request is for emergency services.
+         */
+        public boolean isEmergency() {
+            return mIsEmergency;
+        }
+
+        /**
+         * @return {@code true} if the request caused the device to move out of airplane mode.
+         */
+        public boolean isExitedFromAirplaneMode() {
+            return mIsExitedFromAirplaneMode;
+        }
+
+        /*
+        public @Nullable UtAttributes getUtAttributes();
+            return mUtAttributes;
+        }
+        */
+
+        /**
+         * @return The PS disconnect cause if trying over PS resulted in a failure and
+         *         reselection is required.
+         */
+        public @Nullable ImsReasonInfo getPsDisconnectCause() {
+            return mImsReasonInfo;
+        }
+
+        /**
+         * @return The CS disconnect cause if trying over CS resulted in a failure and
+         *         reselection is required.
+         */
+        public @PreciseDisconnectCauses int getCsDisconnectCause() {
+            return mCause;
+        }
+
+        /**
+         * @return The current registration state of cellular network.
+         */
+        public @Nullable EmergencyRegResult getEmergencyRegResult() {
+            return mEmergencyRegResult;
+        }
+
+        @Override
+        public @NonNull String toString() {
+            return "{ slotId=" + mSlotId
+                    + ", subId=" + mSubId
+                    + ", callId=" + mCallId
+                    + ", number=" + (Build.IS_DEBUGGABLE ? mNumber : "***")
+                    + ", type=" + mSelectorType
+                    + ", videoCall=" + mIsVideoCall
+                    + ", emergency=" + mIsEmergency
+                    + ", airplaneMode=" + mIsExitedFromAirplaneMode
+                    + ", reasonInfo=" + mImsReasonInfo
+                    + ", cause=" + mCause
+                    + ", regResult=" + mEmergencyRegResult
+                    + " }";
+        }
+
+        @Override
+        public boolean equals(Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+            SelectionAttributes that = (SelectionAttributes) o;
+            return mSlotId == that.mSlotId && mSubId == that.mSubId
+                    && TextUtils.equals(mCallId, that.mCallId)
+                    && TextUtils.equals(mNumber, that.mNumber)
+                    && mSelectorType == that.mSelectorType && mIsVideoCall == that.mIsVideoCall
+                    && mIsEmergency == that.mIsEmergency
+                    && mIsExitedFromAirplaneMode == that.mIsExitedFromAirplaneMode
+                    //&& equalsHandlesNulls(mUtAttributes, that.mUtAttributes)
+                    && equalsHandlesNulls(mImsReasonInfo, that.mImsReasonInfo)
+                    && mCause == that.mCause
+                    && equalsHandlesNulls(mEmergencyRegResult, that.mEmergencyRegResult);
+        }
+
+        @Override
+        public int hashCode() {
+            return Objects.hash(mCallId, mNumber, mImsReasonInfo,
+                    mIsVideoCall, mIsEmergency, mIsExitedFromAirplaneMode, mEmergencyRegResult,
+                    mSlotId, mSubId, mSelectorType, mCause);
+        }
+
+        @Override
+        public int describeContents() {
+            return 0;
+        }
+
+        @Override
+        public void writeToParcel(@NonNull Parcel out, int flags) {
+            out.writeInt(mSlotId);
+            out.writeInt(mSubId);
+            out.writeString8(mCallId);
+            out.writeString8(mNumber);
+            out.writeInt(mSelectorType);
+            out.writeBoolean(mIsVideoCall);
+            out.writeBoolean(mIsEmergency);
+            out.writeBoolean(mIsExitedFromAirplaneMode);
+            //out.writeParcelable(mUtAttributes, 0);
+            out.writeParcelable(mImsReasonInfo, 0);
+            out.writeInt(mCause);
+            out.writeParcelable(mEmergencyRegResult, 0);
+        }
+
+        private void readFromParcel(@NonNull Parcel in) {
+            mSlotId = in.readInt();
+            mSubId = in.readInt();
+            mCallId = in.readString8();
+            mNumber = in.readString8();
+            mSelectorType = in.readInt();
+            mIsVideoCall = in.readBoolean();
+            mIsEmergency = in.readBoolean();
+            mIsExitedFromAirplaneMode = in.readBoolean();
+            //mUtAttributes = s.mUtAttributes;
+            mImsReasonInfo = in.readParcelable(ImsReasonInfo.class.getClassLoader(),
+                    android.telephony.ims.ImsReasonInfo.class);
+            mCause = in.readInt();
+            mEmergencyRegResult = in.readParcelable(EmergencyRegResult.class.getClassLoader(),
+                    EmergencyRegResult.class);
+        }
+
+        public static final @NonNull Creator<SelectionAttributes> CREATOR =
+                new Creator<SelectionAttributes>() {
+            @Override
+            public SelectionAttributes createFromParcel(@NonNull Parcel in) {
+                return new SelectionAttributes(in);
+            }
+
+            @Override
+            public SelectionAttributes[] newArray(int size) {
+                return new SelectionAttributes[size];
+            }
+        };
+
+        private static boolean equalsHandlesNulls(Object a, Object b) {
+            return (a == null) ? (b == null) : a.equals(b);
+        }
+
+        /**
+         * Builder class creating a new instance.
+         */
+        public static final class Builder {
+            private final int mSlotId;
+            private final int mSubId;
+            private @Nullable String mCallId;
+            private @Nullable String mNumber;
+            private final @SelectorType int mSelectorType;
+            private boolean mIsVideoCall;
+            private boolean mIsEmergency;
+            private boolean mIsExitedFromAirplaneMode;
+            //private @Nullable UtAttributes mUtAttributes;
+            private @Nullable ImsReasonInfo mImsReasonInfo;
+            private @PreciseDisconnectCauses int mCause;
+            private @Nullable EmergencyRegResult mEmergencyRegResult;
+
+            /**
+             * Default constructor for Builder.
+             */
+            public Builder(int slotId, int subId, @SelectorType int selectorType) {
+                mSlotId = slotId;
+                mSubId = subId;
+                mSelectorType = selectorType;
+            }
+
+            /**
+             * Sets the call identifier.
+             *
+             * @param callId The call identifier.
+             * @return The same instance of the builder.
+             */
+            public @NonNull Builder setCallId(@NonNull String callId) {
+                mCallId = callId;
+                return this;
+            }
+
+            /**
+             * Sets the dialed number.
+             *
+             * @param number The dialed number.
+             * @return The same instance of the builder.
+             */
+            public @NonNull Builder setNumber(@NonNull String number) {
+                mNumber = number;
+                return this;
+            }
+
+            /**
+             * Sets whether it's a video call or not.
+             *
+             * @param video Indicates it's a video call.
+             * @return The same instance of the builder.
+             */
+            public @NonNull Builder setVideoCall(boolean video) {
+                mIsVideoCall = video;
+                return this;
+            }
+
+            /**
+             * Sets whether it's an emergency service or not.
+             *
+             * @param emergency Indicates it's emergency service.
+             * @return The same instance of the builder.
+             */
+            public @NonNull Builder setEmergency(boolean emergency) {
+                mIsEmergency = emergency;
+                return this;
+            }
+
+            /**
+             * Sets whether the request caused the device to move out of airplane mode.
+             *
+             * @param exited {@code true} if the request caused the device to move out of
+             *        airplane mode.
+             * @return The same instance of the builder.
+             */
+            public @NonNull Builder setExitedFromAirplaneMode(boolean exited) {
+                mIsExitedFromAirplaneMode = exited;
+                return this;
+            }
+
+            /**
+             * Sets the Ut service attributes.
+             * Only applicable for SELECTOR_TYPE_UT
+             *
+             * @param attr Ut services attributes.
+             * @return The same instance of the builder.
+             */
+            /*
+            public @NonNull Builder setUtAttributes(@NonNull UtAttributes attr);
+                mUtAttributes = attr;
+                return this;
+            }
+            */
+
+            /**
+             * Sets an optional reason why the last PS attempt failed.
+             *
+             * @param info The reason why the last PS attempt failed.
+             * @return The same instance of the builder.
+             */
+            public @NonNull Builder setPsDisconnectCause(@NonNull ImsReasonInfo info) {
+                mImsReasonInfo = info;
+                return this;
+            }
+
+            /**
+             * Sets an optional reason why the last CS attempt failed.
+             *
+             * @param cause The reason why the last CS attempt failed.
+             * @return The same instance of the builder.
+             */
+            public @NonNull Builder setCsDisconnectCause(@PreciseDisconnectCauses int cause) {
+                mCause = cause;
+                return this;
+            }
+
+            /**
+             * Sets the current registration result for emergency services.
+             *
+             * @param regResult The current registration result for emergency services.
+             * @return The same instance of the builder.
+             */
+            public @NonNull Builder setEmergencyRegResult(@NonNull EmergencyRegResult regResult) {
+                mEmergencyRegResult = regResult;
+                return this;
+            }
+
+            /**
+             * Build the SelectionAttributes.
+             * @return The SelectionAttributes object.
+             */
+            public @NonNull SelectionAttributes build() {
+                return new SelectionAttributes(mSlotId, mSubId, mCallId, mNumber, mSelectorType,
+                        mIsVideoCall, mIsEmergency, mIsExitedFromAirplaneMode, /*mUtAttributes,*/
+                        mImsReasonInfo, mCause, mEmergencyRegResult);
+            }
+        }
+    }
+
+    /**
+     * A wrapper class for ITransportSelectorCallback interface.
+     */
+    private final class TransportSelectorCallbackWrapper implements TransportSelectorCallback {
+        private static final String TAG = "TransportSelectorCallbackWrapper";
+
+        private final @NonNull ITransportSelectorCallback mCallback;
+        private final @NonNull Executor mExecutor;
+
+        private @Nullable ITransportSelectorResultCallbackAdapter mResultCallback;
+        private @Nullable DomainSelectorWrapper mSelectorWrapper;
+
+        TransportSelectorCallbackWrapper(@NonNull ITransportSelectorCallback cb,
+                @NonNull Executor executor) {
+            mCallback = cb;
+            mExecutor = executor;
+        }
+
+        @Override
+        public void onCreated(@NonNull DomainSelector selector) {
+            try {
+                mSelectorWrapper = new DomainSelectorWrapper(selector, mExecutor);
+                mCallback.onCreated(mSelectorWrapper.getCallbackBinder());
+            } catch (Exception e) {
+                Rlog.e(TAG, "onCreated e=" + e);
+            }
+        }
+
+        @Override
+        public void onWlanSelected() {
+            try {
+                mCallback.onWlanSelected();
+            } catch (Exception e) {
+                Rlog.e(TAG, "onWlanSelected e=" + e);
+            }
+        }
+
+        @Override
+        public @NonNull WwanSelectorCallback onWwanSelected() {
+            WwanSelectorCallback callback = null;
+            try {
+                IWwanSelectorCallback cb = mCallback.onWwanSelected();
+                callback = new WwanSelectorCallbackWrapper(cb, mExecutor);
+            } catch (Exception e) {
+                Rlog.e(TAG, "onWwanSelected e=" + e);
+            }
+
+            return callback;
+        }
+
+        @Override
+        public void onWwanSelected(Consumer<WwanSelectorCallback> consumer) {
+            try {
+                mResultCallback = new ITransportSelectorResultCallbackAdapter(consumer, mExecutor);
+                mCallback.onWwanSelectedAsync(mResultCallback);
+            } catch (Exception e) {
+                Rlog.e(TAG, "onWwanSelected e=" + e);
+                executeMethodAsyncNoException(mExecutor,
+                        () -> consumer.accept(null), TAG, "onWwanSelectedAsync-Exception");
+            }
+        }
+
+        @Override
+        public void onSelectionTerminated(@DisconnectCauses int cause) {
+            try {
+                mCallback.onSelectionTerminated(cause);
+                mSelectorWrapper = null;
+            } catch (Exception e) {
+                Rlog.e(TAG, "onSelectionTerminated e=" + e);
+            }
+        }
+
+        private class ITransportSelectorResultCallbackAdapter
+                extends ITransportSelectorResultCallback.Stub {
+            private final @NonNull Consumer<WwanSelectorCallback> mConsumer;
+            private final @NonNull Executor mExecutor;
+
+            ITransportSelectorResultCallbackAdapter(
+                    @NonNull Consumer<WwanSelectorCallback> consumer,
+                    @NonNull Executor executor) {
+                mConsumer = consumer;
+                mExecutor = executor;
+            }
+
+            @Override
+            public void onCompleted(@NonNull IWwanSelectorCallback cb) {
+                if (mConsumer == null) return;
+
+                WwanSelectorCallback callback = new WwanSelectorCallbackWrapper(cb, mExecutor);
+                executeMethodAsyncNoException(mExecutor,
+                        () -> mConsumer.accept(callback), TAG, "onWwanSelectedAsync-Completed");
+            }
+        }
+    }
+
+    /**
+     * A wrapper class for IDomainSelector interface.
+     */
+    private final class DomainSelectorWrapper {
+        private static final String TAG = "DomainSelectorWrapper";
+
+        private @NonNull IDomainSelector mCallbackBinder;
+
+        DomainSelectorWrapper(@NonNull DomainSelector cb, @NonNull Executor executor) {
+            mCallbackBinder = new IDomainSelectorAdapter(cb, executor);
+        }
+
+        private class IDomainSelectorAdapter extends IDomainSelector.Stub {
+            private final @NonNull WeakReference<DomainSelector> mDomainSelectorWeakRef;
+            private final @NonNull Executor mExecutor;
+
+            IDomainSelectorAdapter(@NonNull DomainSelector domainSelector,
+                    @NonNull Executor executor) {
+                mDomainSelectorWeakRef =
+                        new WeakReference<DomainSelector>(domainSelector);
+                mExecutor = executor;
+            }
+
+            @Override
+            public void cancelSelection() {
+                final DomainSelector domainSelector = mDomainSelectorWeakRef.get();
+                if (domainSelector == null) return;
+
+                executeMethodAsyncNoException(mExecutor,
+                        () -> domainSelector.cancelSelection(), TAG, "cancelSelection");
+            }
+
+            @Override
+            public void reselectDomain(@NonNull SelectionAttributes attr) {
+                final DomainSelector domainSelector = mDomainSelectorWeakRef.get();
+                if (domainSelector == null) return;
+
+                executeMethodAsyncNoException(mExecutor,
+                        () -> domainSelector.reselectDomain(attr), TAG, "reselectDomain");
+            }
+
+            @Override
+            public void finishSelection() {
+                final DomainSelector domainSelector = mDomainSelectorWeakRef.get();
+                if (domainSelector == null) return;
+
+                executeMethodAsyncNoException(mExecutor,
+                        () -> domainSelector.finishSelection(), TAG, "finishSelection");
+            }
+        }
+
+        public @NonNull IDomainSelector getCallbackBinder() {
+            return mCallbackBinder;
+        }
+    }
+
+    /**
+     * A wrapper class for IWwanSelectorCallback and IWwanSelectorResultCallback.
+     */
+    private final class WwanSelectorCallbackWrapper
+            implements WwanSelectorCallback, CancellationSignal.OnCancelListener {
+        private static final String TAG = "WwanSelectorCallbackWrapper";
+
+        private final @NonNull IWwanSelectorCallback mCallback;
+        private final @NonNull Executor mExecutor;
+
+        private @Nullable IWwanSelectorResultCallbackAdapter mResultCallback;
+
+        WwanSelectorCallbackWrapper(@NonNull IWwanSelectorCallback cb,
+                @NonNull Executor executor) {
+            mCallback = cb;
+            mExecutor = executor;
+        }
+
+        @Override
+        public void onCancel() {
+            try {
+                mCallback.onCancel();
+            } catch (Exception e) {
+                Rlog.e(TAG, "onCancel e=" + e);
+            }
+        }
+
+        @Override
+        public void onRequestEmergencyNetworkScan(@NonNull List<Integer> preferredNetworks,
+                @EmergencyScanType int scanType, @NonNull CancellationSignal signal,
+                @NonNull Consumer<EmergencyRegResult> consumer) {
+            try {
+                if (signal != null) signal.setOnCancelListener(this);
+                mResultCallback = new IWwanSelectorResultCallbackAdapter(consumer, mExecutor);
+                mCallback.onRequestEmergencyNetworkScan(
+                        preferredNetworks.stream().mapToInt(Integer::intValue).toArray(),
+                        scanType, mResultCallback);
+            } catch (Exception e) {
+                Rlog.e(TAG, "onRequestEmergencyNetworkScan e=" + e);
+            }
+        }
+
+        @Override
+        public void onDomainSelected(@RadioAccessNetworkType int accessNetworkType) {
+            try {
+                mCallback.onDomainSelected(accessNetworkType);
+            } catch (Exception e) {
+                Rlog.e(TAG, "onDomainSelected e=" + e);
+            }
+        }
+
+        private class IWwanSelectorResultCallbackAdapter
+                extends IWwanSelectorResultCallback.Stub {
+            private final @NonNull Consumer<EmergencyRegResult> mConsumer;
+            private final @NonNull Executor mExecutor;
+
+            IWwanSelectorResultCallbackAdapter(@NonNull Consumer<EmergencyRegResult> consumer,
+                    @NonNull Executor executor) {
+                mConsumer = consumer;
+                mExecutor = executor;
+            }
+
+            @Override
+            public void onComplete(@NonNull EmergencyRegResult result) {
+                if (mConsumer == null) return;
+
+                executeMethodAsyncNoException(mExecutor,
+                        () -> mConsumer.accept(result), TAG, "onScanComplete");
+            }
+        }
+    }
+
+    private final Object mExecutorLock = new Object();
+
+    /** Executor used to execute methods called remotely by the framework. */
+    private @NonNull Executor mExecutor;
+
+    /**
+     * Selects a domain for the given operation.
+     *
+     * @param attr Required to determine the domain.
+     * @param callback The callback instance being registered.
+     */
+    public void onDomainSelection(@NonNull SelectionAttributes attr,
+            @NonNull TransportSelectorCallback callback) {
+    }
+
+    /**
+     * Notifies the change in {@link ServiceState} for a specific slot.
+     *
+     * @param slotId For which the state changed.
+     * @param subId For which the state changed.
+     * @param serviceState Updated {@link ServiceState}.
+     */
+    public void onServiceStateUpdated(int slotId, int subId, @NonNull ServiceState serviceState) {
+    }
+
+    /**
+     * Notifies the change in {@link BarringInfo} for a specific slot.
+     *
+     * @param slotId For which the state changed.
+     * @param subId For which the state changed.
+     * @param info Updated {@link BarringInfo}.
+     */
+    public void onBarringInfoUpdated(int slotId, int subId, @NonNull BarringInfo info) {
+    }
+
+    private final IBinder mDomainSelectionServiceController =
+            new IDomainSelectionServiceController.Stub() {
+        @Override
+        public void selectDomain(@NonNull SelectionAttributes attr,
+                @NonNull ITransportSelectorCallback callback)  throws RemoteException {
+            executeMethodAsync(getCachedExecutor(),
+                    () -> DomainSelectionService.this.onDomainSelection(attr,
+                            new TransportSelectorCallbackWrapper(callback, getCachedExecutor())),
+                    LOG_TAG, "onDomainSelection");
+        }
+
+        @Override
+        public void updateServiceState(int slotId, int subId, @NonNull ServiceState serviceState) {
+            executeMethodAsyncNoException(getCachedExecutor(),
+                    () -> DomainSelectionService.this.onServiceStateUpdated(slotId,
+                            subId, serviceState), LOG_TAG, "onServiceStateUpdated");
+        }
+
+        @Override
+        public void updateBarringInfo(int slotId, int subId, @NonNull BarringInfo info) {
+            executeMethodAsyncNoException(getCachedExecutor(),
+                    () -> DomainSelectionService.this.onBarringInfoUpdated(slotId, subId, info),
+                    LOG_TAG, "onBarringInfoUpdated");
+        }
+    };
+
+    private static void executeMethodAsync(@NonNull Executor executor, @NonNull Runnable r,
+            @NonNull String tag, @NonNull String errorLogName) throws RemoteException {
+        try {
+            CompletableFuture.runAsync(
+                    () -> TelephonyUtils.runWithCleanCallingIdentity(r), executor).join();
+        } catch (CancellationException | CompletionException e) {
+            Rlog.w(tag, "Binder - " + errorLogName + " exception: " + e.getMessage());
+            throw new RemoteException(e.getMessage());
+        }
+    }
+
+    private void executeMethodAsyncNoException(@NonNull Executor executor, @NonNull Runnable r,
+            @NonNull String tag, @NonNull String errorLogName) {
+        try {
+            CompletableFuture.runAsync(
+                    () -> TelephonyUtils.runWithCleanCallingIdentity(r), executor).join();
+        } catch (CancellationException | CompletionException e) {
+            Rlog.w(tag, "Binder - " + errorLogName + " exception: " + e.getMessage());
+        }
+    }
+
+    /** @hide */
+    @Override
+    public IBinder onBind(Intent intent) {
+        if (SERVICE_INTERFACE.equals(intent.getAction())) {
+            Log.i(LOG_TAG, "DomainSelectionService Bound.");
+            return mDomainSelectionServiceController;
+        }
+        return null;
+    }
+
+    /**
+     * The DomainSelectionService will be able to define an {@link Executor} that the service
+     * can use to execute the methods. It has set the default executor as Runnable::run,
+     *
+     * @return An {@link Executor} to be used.
+     */
+    @SuppressLint("OnNameExpected")
+    public @NonNull Executor getExecutor() {
+        return Runnable::run;
+    }
+
+    private @NonNull Executor getCachedExecutor() {
+        synchronized (mExecutorLock) {
+            if (mExecutor == null) {
+                Executor e = getExecutor();
+                mExecutor = (e != null) ? e : Runnable::run;
+            }
+            return mExecutor;
+        }
+    }
+
+    /**
+     * Returns a string representation of the domain.
+     * @param domain The domain.
+     * @return The name of the domain.
+     * @hide
+     */
+    public static @NonNull String getDomainName(@NetworkRegistrationInfo.Domain int domain) {
+        return NetworkRegistrationInfo.domainToString(domain);
+    }
+}
diff --git a/telephony/java/android/telephony/DomainSelector.java b/telephony/java/android/telephony/DomainSelector.java
new file mode 100644
index 0000000..0871831
--- /dev/null
+++ b/telephony/java/android/telephony/DomainSelector.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import android.annotation.NonNull;
+import android.telephony.DomainSelectionService.SelectionAttributes;
+
+/**
+ * Implemented as part of the {@link DomainSelectionService} to implement domain selection
+ * for a specific use case.
+ * @hide
+ */
+public interface DomainSelector {
+    /**
+     * Cancel an ongoing selection operation. It is up to the DomainSelectionService
+     * to clean up all ongoing operations with the framework.
+     */
+    void cancelSelection();
+
+    /**
+     * Reselect a domain due to the call not setting up properly.
+     *
+     * @param attr attributes required to select the domain.
+     */
+    void reselectDomain(@NonNull SelectionAttributes attr);
+
+    /**
+     * Finish the selection procedure and clean everything up.
+     */
+    void finishSelection();
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl b/telephony/java/android/telephony/EmergencyRegResult.aidl
similarity index 75%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
copy to telephony/java/android/telephony/EmergencyRegResult.aidl
index 1550ab3..f722962 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
+++ b/telephony/java/android/telephony/EmergencyRegResult.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2022 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.
@@ -11,9 +11,9 @@
  * 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.
+ * limitations under the License
  */
 
-package com.android.systemui.shared.system;
+package android.telephony;
 
-parcelable RemoteTransitionCompat;
+parcelable EmergencyRegResult;
diff --git a/telephony/java/android/telephony/EmergencyRegResult.java b/telephony/java/android/telephony/EmergencyRegResult.java
new file mode 100644
index 0000000..5aed412
--- /dev/null
+++ b/telephony/java/android/telephony/EmergencyRegResult.java
@@ -0,0 +1,318 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.text.TextUtils;
+
+import java.util.Objects;
+
+/**
+ * Contains attributes required to determine the domain for a telephony service
+ * @hide
+ */
+public final class EmergencyRegResult implements Parcelable {
+
+    /**
+     * Indicates the cellular network type of the acquired system.
+     */
+    private @AccessNetworkConstants.RadioAccessNetworkType int mAccessNetworkType;
+
+    /**
+     * Registration state of the acquired system.
+     */
+    private @NetworkRegistrationInfo.RegistrationState int mRegState;
+
+    /**
+     * EMC domain indicates the current domain of the acquired system.
+     */
+    private @NetworkRegistrationInfo.Domain int mDomain;
+
+    /**
+     * Indicates whether the network supports voice over PS network.
+     */
+    private boolean mIsVopsSupported;
+
+    /**
+     * This indicates if camped network support VoLTE emergency bearers.
+     * This should only be set if the UE is in LTE mode.
+     */
+    private boolean mIsEmcBearerSupported;
+
+    /**
+     * The value of the network provided EMC in 5G Registration ACCEPT.
+     * This should be set only if the UE is in 5G mode.
+     */
+    private int mNwProvidedEmc;
+
+    /**
+     * The value of the network provided EMF(EPS Fallback) in 5G Registration ACCEPT.
+     * This should be set only if the UE is in 5G mode.
+     */
+    private int mNwProvidedEmf;
+
+    /** 3-digit Mobile Country Code, 000..999, empty string if unknown. */
+    private @NonNull String mMcc;
+
+    /** 2 or 3-digit Mobile Network Code, 00..999, empty string if unknown. */
+    private @NonNull String mMnc;
+
+    /**
+     * The ISO-3166-1 alpha-2 country code equivalent for the network's country code,
+     * empty string if unknown.
+     */
+    private @NonNull String mIso;
+
+    /**
+     * Constructor
+     * @param accessNetwork Indicates the network type of the acquired system.
+     * @param regState Indicates the registration state of the acquired system.
+     * @param domain Indicates the current domain of the acquired system.
+     * @param isVopsSupported Indicates whether the network supports voice over PS network.
+     * @param isEmcBearerSupported  Indicates if camped network support VoLTE emergency bearers.
+     * @param emc The value of the network provided EMC in 5G Registration ACCEPT.
+     * @param emf The value of the network provided EMF(EPS Fallback) in 5G Registration ACCEPT.
+     * @param mcc Mobile country code, empty string if unknown.
+     * @param mnc Mobile network code, empty string if unknown.
+     * @param iso The ISO-3166-1 alpha-2 country code equivalent, empty string if unknown.
+     * @hide
+     */
+    public EmergencyRegResult(
+            @AccessNetworkConstants.RadioAccessNetworkType int accessNetwork,
+            @NetworkRegistrationInfo.RegistrationState int regState,
+            @NetworkRegistrationInfo.Domain int domain,
+            boolean isVopsSupported, boolean isEmcBearerSupported, int emc, int emf,
+            @NonNull String mcc, @NonNull String mnc, @NonNull String iso) {
+        mAccessNetworkType = accessNetwork;
+        mRegState = regState;
+        mDomain = domain;
+        mIsVopsSupported = isVopsSupported;
+        mIsEmcBearerSupported = isEmcBearerSupported;
+        mNwProvidedEmc = emc;
+        mNwProvidedEmf = emf;
+        mMcc = mcc;
+        mMnc = mnc;
+        mIso = iso;
+    }
+
+    /**
+     * Copy constructors
+     *
+     * @param s Source emergency scan result
+     * @hide
+     */
+    public EmergencyRegResult(@NonNull EmergencyRegResult s) {
+        mAccessNetworkType = s.mAccessNetworkType;
+        mRegState = s.mRegState;
+        mDomain = s.mDomain;
+        mIsVopsSupported = s.mIsVopsSupported;
+        mIsEmcBearerSupported = s.mIsEmcBearerSupported;
+        mNwProvidedEmc = s.mNwProvidedEmc;
+        mNwProvidedEmf = s.mNwProvidedEmf;
+        mMcc = s.mMcc;
+        mMnc = s.mMnc;
+        mIso = s.mIso;
+    }
+
+    /**
+     * Construct a EmergencyRegResult object from the given parcel.
+     */
+    private EmergencyRegResult(@NonNull Parcel in) {
+        readFromParcel(in);
+    }
+
+    /**
+     * Returns the cellular access network type of the acquired system.
+     *
+     * @return the cellular network type.
+     */
+    public @AccessNetworkConstants.RadioAccessNetworkType int getAccessNetwork() {
+        return mAccessNetworkType;
+    }
+
+    /**
+     * Returns the registration state of the acquired system.
+     *
+     * @return the registration state.
+     */
+    public @NetworkRegistrationInfo.RegistrationState int getRegState() {
+        return mRegState;
+    }
+
+    /**
+     * Returns the current domain of the acquired system.
+     *
+     * @return the current domain.
+     */
+    public @NetworkRegistrationInfo.Domain int getDomain() {
+        return mDomain;
+    }
+
+    /**
+     * Returns whether the network supports voice over PS network.
+     *
+     * @return {@code true} if the network supports voice over PS network.
+     */
+    public boolean isVopsSupported() {
+        return mIsVopsSupported;
+    }
+
+    /**
+     * Returns whether camped network support VoLTE emergency bearers.
+     * This is not valid if the UE is not in LTE mode.
+     *
+     * @return {@code true} if the network supports VoLTE emergency bearers.
+     */
+    public boolean isEmcBearerSupported() {
+        return mIsEmcBearerSupported;
+    }
+
+    /**
+     * Returns the value of the network provided EMC in 5G Registration ACCEPT.
+     * This is not valid if UE is not in 5G mode.
+     *
+     * @return the value of the network provided EMC.
+     */
+    public int getNwProvidedEmc() {
+        return mNwProvidedEmc;
+    }
+
+    /**
+     * Returns the value of the network provided EMF(EPS Fallback) in 5G Registration ACCEPT.
+     * This is not valid if UE is not in 5G mode.
+     *
+     * @return the value of the network provided EMF.
+     */
+    public int getNwProvidedEmf() {
+        return mNwProvidedEmf;
+    }
+
+    /**
+     * Returns 3-digit Mobile Country Code.
+     *
+     * @return Mobile Country Code.
+     */
+    public @NonNull String getMcc() {
+        return mMcc;
+    }
+
+    /**
+     * Returns 2 or 3-digit Mobile Network Code.
+     *
+     * @return Mobile Network Code.
+     */
+    public @NonNull String getMnc() {
+        return mMnc;
+    }
+
+    /**
+     * Returns the ISO-3166-1 alpha-2 country code is provided in lowercase 2 character format.
+     *
+     * @return Country code.
+     */
+    public @NonNull String getIso() {
+        return mIso;
+    }
+
+    @Override
+    public @NonNull String toString() {
+        return "{ accessNetwork="
+                + AccessNetworkConstants.AccessNetworkType.toString(mAccessNetworkType)
+                + ", regState=" + NetworkRegistrationInfo.registrationStateToString(mRegState)
+                + ", domain=" + NetworkRegistrationInfo.domainToString(mDomain)
+                + ", vops=" + mIsVopsSupported
+                + ", emcBearer=" + mIsEmcBearerSupported
+                + ", emc=" + mNwProvidedEmc
+                + ", emf=" + mNwProvidedEmf
+                + ", mcc=" + mMcc
+                + ", mnc=" + mMnc
+                + ", iso=" + mIso
+                + " }";
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        EmergencyRegResult that = (EmergencyRegResult) o;
+        return mAccessNetworkType == that.mAccessNetworkType
+                && mRegState == that.mRegState
+                && mDomain == that.mDomain
+                && mIsVopsSupported == that.mIsVopsSupported
+                && mIsEmcBearerSupported == that.mIsEmcBearerSupported
+                && mNwProvidedEmc == that.mNwProvidedEmc
+                && mNwProvidedEmf == that.mNwProvidedEmf
+                && TextUtils.equals(mMcc, that.mMcc)
+                && TextUtils.equals(mMnc, that.mMnc)
+                && TextUtils.equals(mIso, that.mIso);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mAccessNetworkType, mRegState, mDomain,
+                mIsVopsSupported, mIsEmcBearerSupported,
+                mNwProvidedEmc, mNwProvidedEmf,
+                mMcc, mMnc, mIso);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        out.writeInt(mAccessNetworkType);
+        out.writeInt(mRegState);
+        out.writeInt(mDomain);
+        out.writeBoolean(mIsVopsSupported);
+        out.writeBoolean(mIsEmcBearerSupported);
+        out.writeInt(mNwProvidedEmc);
+        out.writeInt(mNwProvidedEmf);
+        out.writeString8(mMcc);
+        out.writeString8(mMnc);
+        out.writeString8(mIso);
+    }
+
+    private void readFromParcel(@NonNull Parcel in) {
+        mAccessNetworkType = in.readInt();
+        mRegState = in.readInt();
+        mDomain = in.readInt();
+        mIsVopsSupported = in.readBoolean();
+        mIsEmcBearerSupported = in.readBoolean();
+        mNwProvidedEmc = in.readInt();
+        mNwProvidedEmf = in.readInt();
+        mMcc = in.readString8();
+        mMnc = in.readString8();
+        mIso = in.readString8();
+    }
+
+    public static final @NonNull Creator<EmergencyRegResult> CREATOR =
+            new Creator<EmergencyRegResult>() {
+        @Override
+        public EmergencyRegResult createFromParcel(@NonNull Parcel in) {
+            return new EmergencyRegResult(in);
+        }
+
+        @Override
+        public EmergencyRegResult[] newArray(int size) {
+            return new EmergencyRegResult[size];
+        }
+    };
+}
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index d670e55..1f301c1 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -2268,7 +2268,21 @@
             RESULT_RIL_SIM_ABSENT,
             RESULT_RIL_SIMULTANEOUS_SMS_AND_CALL_NOT_ALLOWED,
             RESULT_RIL_ACCESS_BARRED,
-            RESULT_RIL_BLOCKED_DUE_TO_CALL
+            RESULT_RIL_BLOCKED_DUE_TO_CALL,
+            RESULT_RIL_GENERIC_ERROR,
+            RESULT_RIL_INVALID_RESPONSE,
+            RESULT_RIL_SIM_PIN2,
+            RESULT_RIL_SIM_PUK2,
+            RESULT_RIL_SUBSCRIPTION_NOT_AVAILABLE,
+            RESULT_RIL_SIM_ERROR,
+            RESULT_RIL_INVALID_SIM_STATE,
+            RESULT_RIL_NO_SMS_TO_ACK,
+            RESULT_RIL_SIM_BUSY,
+            RESULT_RIL_SIM_FULL,
+            RESULT_RIL_NO_SUBSCRIPTION,
+            RESULT_RIL_NO_NETWORK_FOUND,
+            RESULT_RIL_DEVICE_IN_USE,
+            RESULT_RIL_ABORTED
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface Result {}
@@ -2534,7 +2548,7 @@
     public static final int RESULT_RIL_SIM_ABSENT = 120;
 
     /**
-     * 1X voice and SMS are not allowed simulteneously.
+     * 1X voice and SMS are not allowed simultaneously.
      */
     public static final int RESULT_RIL_SIMULTANEOUS_SMS_AND_CALL_NOT_ALLOWED = 121;
 
@@ -2553,6 +2567,73 @@
      */
     public static final int RESULT_RIL_GENERIC_ERROR = 124;
 
+    /**
+     * A RIL internal error when one of the RIL layers receives an unrecognized response from a
+     * lower layer.
+     */
+    public static final int RESULT_RIL_INVALID_RESPONSE = 125;
+
+    /**
+     * Operation requires SIM PIN2 to be entered
+     */
+    public static final int RESULT_RIL_SIM_PIN2 = 126;
+
+    /**
+     * Operation requires SIM PUK2 to be entered
+     */
+    public static final int RESULT_RIL_SIM_PUK2 = 127;
+
+    /**
+     * Fail to find CDMA subscription from specified location
+     */
+    public static final int RESULT_RIL_SUBSCRIPTION_NOT_AVAILABLE = 128;
+
+    /**
+     * Received error from SIM card
+     */
+    public static final int RESULT_RIL_SIM_ERROR = 129;
+
+    /**
+     * Cannot process the request in current SIM state
+     */
+    public static final int RESULT_RIL_INVALID_SIM_STATE = 130;
+
+    /**
+     * ACK received when there is no SMS to ack
+     */
+    public static final int RESULT_RIL_NO_SMS_TO_ACK = 131;
+
+    /**
+     * SIM is busy
+     */
+    public static final int RESULT_RIL_SIM_BUSY = 132;
+
+    /**
+     * The target EF is full
+     */
+    public static final int RESULT_RIL_SIM_FULL = 133;
+
+    /**
+     * Device does not have subscription
+     */
+    public static final int RESULT_RIL_NO_SUBSCRIPTION = 134;
+
+    /**
+     * Network cannot be found
+     */
+    public static final int RESULT_RIL_NO_NETWORK_FOUND = 135;
+
+    /**
+     * Operation cannot be performed because the device is currently in use
+     */
+    public static final int RESULT_RIL_DEVICE_IN_USE = 136;
+
+    /**
+     * Operation aborted
+     */
+    public static final int RESULT_RIL_ABORTED = 137;
+
+
     // SMS receiving results sent as a "result" extra in {@link Intents.SMS_REJECTED_ACTION}
 
     /**
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index ef693b5..76a145c 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -4002,6 +4002,10 @@
      * cautiously, for example, after formatting the number to a consistent format with
      * {@link android.telephony.PhoneNumberUtils#formatNumberToE164(String, String)}.
      *
+     * <p>The availability and correctness of the phone number depends on the underlying source
+     * and the network etc. Additional verification is needed to use this number for
+     * security-related or other sensitive scenarios.
+     *
      * @param subscriptionId the subscription ID, or {@link #DEFAULT_SUBSCRIPTION_ID}
      *                       for the default one.
      * @return the phone number, or an empty string if not available.
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 9ecebf1..35b2055 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -3323,15 +3323,14 @@
             case NETWORK_TYPE_TD_SCDMA:
                 return NETWORK_TYPE_BITMASK_TD_SCDMA;
             case NETWORK_TYPE_LTE:
-                return NETWORK_TYPE_BITMASK_LTE;
             case NETWORK_TYPE_LTE_CA:
-                return NETWORK_TYPE_BITMASK_LTE_CA;
+                return NETWORK_TYPE_BITMASK_LTE;
             case NETWORK_TYPE_NR:
                 return NETWORK_TYPE_BITMASK_NR;
             case NETWORK_TYPE_IWLAN:
                 return NETWORK_TYPE_BITMASK_IWLAN;
             case NETWORK_TYPE_IDEN:
-                return (1 << (NETWORK_TYPE_IDEN - 1));
+                return NETWORK_TYPE_BITMASK_IDEN;
             default:
                 return NETWORK_TYPE_BITMASK_UNKNOWN;
         }
@@ -9477,7 +9476,8 @@
             ALLOWED_NETWORK_TYPES_REASON_USER,
             ALLOWED_NETWORK_TYPES_REASON_POWER,
             ALLOWED_NETWORK_TYPES_REASON_CARRIER,
-            ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G
+            ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G,
+            ALLOWED_NETWORK_TYPES_REASON_USER_RESTRICTIONS,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface AllowedNetworkTypesReason {
@@ -9516,6 +9516,15 @@
     public static final int ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G = 3;
 
     /**
+     * To indicate allowed network type change is requested by an update to the
+     * {@link android.os.UserManager.DISALLOW_CELLULAR_2G} user restriction.
+     *
+     * @hide
+     */
+    @SystemApi
+    public static final int ALLOWED_NETWORK_TYPES_REASON_USER_RESTRICTIONS = 4;
+
+    /**
      * Set the allowed network types of the device and provide the reason triggering the allowed
      * network change.
      * <p>Requires permission: android.Manifest.MODIFY_PHONE_STATE or
@@ -9607,6 +9616,7 @@
             case TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_POWER:
             case TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_CARRIER:
             case TelephonyManager.ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G:
+            case ALLOWED_NETWORK_TYPES_REASON_USER_RESTRICTIONS:
                 return true;
         }
         return false;
@@ -13911,7 +13921,8 @@
                     NETWORK_TYPE_BITMASK_LTE,
                     NETWORK_TYPE_BITMASK_LTE_CA,
                     NETWORK_TYPE_BITMASK_NR,
-                    NETWORK_TYPE_BITMASK_IWLAN
+                    NETWORK_TYPE_BITMASK_IWLAN,
+                    NETWORK_TYPE_BITMASK_IDEN
             })
     public @interface NetworkTypeBitMask {}
 
@@ -13971,6 +13982,10 @@
      */
     public static final long NETWORK_TYPE_BITMASK_HSPA = (1 << (NETWORK_TYPE_HSPA -1));
     /**
+     * network type bitmask indicating the support of radio tech iDen.
+     */
+    public static final long NETWORK_TYPE_BITMASK_IDEN = (1 << (NETWORK_TYPE_IDEN - 1));
+    /**
      * network type bitmask indicating the support of radio tech HSPAP.
      */
     public static final long NETWORK_TYPE_BITMASK_HSPAP = (1 << (NETWORK_TYPE_HSPAP -1));
@@ -13988,12 +14003,13 @@
      */
     public static final long NETWORK_TYPE_BITMASK_LTE = (1 << (NETWORK_TYPE_LTE -1));
     /**
-     * NOT USED; this bitmask is exposed accidentally, will be deprecated in U.
+     * NOT USED; this bitmask is exposed accidentally.
      * If used, will be converted to {@link #NETWORK_TYPE_BITMASK_LTE}.
      * network type bitmask indicating the support of radio tech LTE CA (carrier aggregation).
      *
-     * @see #NETWORK_TYPE_BITMASK_LTE
+     * @deprecated Please use {@link #NETWORK_TYPE_BITMASK_LTE} instead.
      */
+    @Deprecated
     public static final long NETWORK_TYPE_BITMASK_LTE_CA = (1 << (NETWORK_TYPE_LTE_CA -1));
 
     /**
@@ -17190,7 +17206,13 @@
     }
 
     /**
-     * Purchase premium capability request was successful. Subsequent attempts will return
+     * Purchase premium capability request was successful.
+     * Once the purchase result is successful, the network must set up a slicing configuration
+     * for the purchased premium capability within the timeout specified by
+     * {@link CarrierConfigManager#KEY_PREMIUM_CAPABILITY_NETWORK_SETUP_TIME_MILLIS_LONG}.
+     * During the setup time, subsequent attempts will return
+     * {@link #PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP}.
+     * After setup is complete, subsequent attempts will return
      * {@link #PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_PURCHASED} until the booster expires.
      * The expiry time is determined by the type or duration of boost purchased from the carrier,
      * provided at {@link CarrierConfigManager#KEY_PREMIUM_CAPABILITY_PURCHASE_URL_STRING}.
@@ -17314,6 +17336,16 @@
     public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA = 14;
 
     /**
+     * Purchase premium capability was successful and is waiting for the network to setup the
+     * slicing configuration. If the setup is complete within the time specified by
+     * {@link CarrierConfigManager#KEY_PREMIUM_CAPABILITY_NETWORK_SETUP_TIME_MILLIS_LONG},
+     * subsequent requests will return {@link #PURCHASE_PREMIUM_CAPABILITY_RESULT_ALREADY_PURCHASED}
+     * until the purchase expires. If the setup is not complete within the time specified above,
+     * applications can reques the premium capability again.
+     */
+    public static final int PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP = 15;
+
+    /**
      * Results of the purchase premium capability request.
      * @hide
      */
@@ -17331,7 +17363,8 @@
             PURCHASE_PREMIUM_CAPABILITY_RESULT_FEATURE_NOT_SUPPORTED,
             PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_NOT_AVAILABLE,
             PURCHASE_PREMIUM_CAPABILITY_RESULT_NETWORK_CONGESTED,
-            PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA})
+            PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA,
+            PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP})
     public @interface PurchasePremiumCapabilityResult {}
 
     /**
@@ -17372,6 +17405,8 @@
                 return "NETWORK_CONGESTED";
             case PURCHASE_PREMIUM_CAPABILITY_RESULT_NOT_DEFAULT_DATA:
                 return "NOT_DEFAULT_DATA";
+            case PURCHASE_PREMIUM_CAPABILITY_RESULT_PENDING_NETWORK_SETUP:
+                return "PENDING_NETWORK_SETUP";
             default:
                 return "UNKNOWN (" + result + ")";
         }
diff --git a/telephony/java/android/telephony/TransportSelectorCallback.java b/telephony/java/android/telephony/TransportSelectorCallback.java
new file mode 100644
index 0000000..d396790
--- /dev/null
+++ b/telephony/java/android/telephony/TransportSelectorCallback.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import android.annotation.NonNull;
+import android.telephony.Annotation.DisconnectCauses;
+
+import java.util.function.Consumer;
+
+/**
+ * A callback class used to receive the transport selection result.
+ * @hide
+ */
+public interface TransportSelectorCallback {
+    /**
+     * Notify that {@link DomainSelector} instance has been created for the selection request.
+     *
+     * @param selector the {@link DomainSelector} instance created.
+     */
+    void onCreated(@NonNull DomainSelector selector);
+
+    /**
+     * Notify that WLAN transport has been selected.
+     */
+    void onWlanSelected();
+
+    /**
+     * Notify that WWAN transport has been selected.
+     */
+    @NonNull WwanSelectorCallback onWwanSelected();
+
+    /**
+     * Notify that WWAN transport has been selected.
+     *
+     * @param consumer The callback to receive the result.
+     */
+    void onWwanSelected(Consumer<WwanSelectorCallback> consumer);
+
+    /**
+     * Notify that selection has terminated because there is no decision that can be made
+     * or a timeout has occurred. The call should be terminated when this method is called.
+     *
+     * @param cause indicates the reason.
+     */
+    void onSelectionTerminated(@DisconnectCauses int cause);
+}
diff --git a/telephony/java/android/telephony/WwanSelectorCallback.java b/telephony/java/android/telephony/WwanSelectorCallback.java
new file mode 100644
index 0000000..489a589
--- /dev/null
+++ b/telephony/java/android/telephony/WwanSelectorCallback.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony;
+
+import android.annotation.NonNull;
+import android.os.CancellationSignal;
+import android.telephony.AccessNetworkConstants.RadioAccessNetworkType;
+import android.telephony.DomainSelectionService.EmergencyScanType;
+
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * A callback class used to receive the domain selection result.
+ * @hide
+ */
+public interface WwanSelectorCallback {
+    /**
+     * Notify the framework that the {@link DomainSelectionService} has requested an emergency
+     * network scan as part of selection.
+     *
+     * @param preferredNetworks the ordered list of preferred networks to scan.
+     * @param scanType indicates the scan preference, such as full service or limited service.
+     * @param signal notifies when the operation is canceled.
+     * @param consumer the handler of the response.
+     */
+    void onRequestEmergencyNetworkScan(@NonNull List<Integer> preferredNetworks,
+            @EmergencyScanType int scanType,
+            @NonNull CancellationSignal signal, @NonNull Consumer<EmergencyRegResult> consumer);
+
+    /**
+     * Notifies the FW that the domain has been selected. After this method is called,
+     * this interface can be discarded.
+     *
+     * @param accessNetworkType the selected network type.
+     */
+    void onDomainSelected(@RadioAccessNetworkType int accessNetworkType);
+}
diff --git a/telephony/java/android/telephony/ims/ImsService.java b/telephony/java/android/telephony/ims/ImsService.java
index f0401534..a3cbb4a 100644
--- a/telephony/java/android/telephony/ims/ImsService.java
+++ b/telephony/java/android/telephony/ims/ImsService.java
@@ -204,6 +204,7 @@
 
     private IImsServiceControllerListener mListener;
     private final Object mListenerLock = new Object();
+    private final Object mExecutorLock = new Object();
     private Executor mExecutor;
 
     /**
@@ -214,10 +215,6 @@
      * vendor use Runnable::run.
      */
     public ImsService() {
-        mExecutor = ImsService.this.getExecutor();
-        if (mExecutor == null) {
-            mExecutor = Runnable::run;
-        }
     }
 
     /**
@@ -356,7 +353,7 @@
                 ImsConfigImplBase c =
                         ImsService.this.getConfigForSubscription(slotId, subId);
                 if (c != null) {
-                    c.setDefaultExecutor(mExecutor);
+                    c.setDefaultExecutor(getCachedExecutor());
                     return c.getIImsConfig();
                 } else {
                     return null;
@@ -370,7 +367,7 @@
                 ImsRegistrationImplBase r =
                         ImsService.this.getRegistrationForSubscription(slotId, subId);
                 if (r != null) {
-                    r.setDefaultExecutor(mExecutor);
+                    r.setDefaultExecutor(getCachedExecutor());
                     return r.getBinder();
                 } else {
                     return null;
@@ -383,7 +380,7 @@
             return executeMethodAsyncForResult(() -> {
                 SipTransportImplBase s =  ImsService.this.getSipTransport(slotId);
                 if (s != null) {
-                    s.setDefaultExecutor(mExecutor);
+                    s.setDefaultExecutor(getCachedExecutor());
                     return s.getBinder();
                 } else {
                     return null;
@@ -427,11 +424,21 @@
         return null;
     }
 
+    private Executor getCachedExecutor() {
+        synchronized (mExecutorLock) {
+            if (mExecutor == null) {
+                Executor e = ImsService.this.getExecutor();
+                mExecutor = (e != null) ? e : Runnable::run;
+            }
+            return mExecutor;
+        }
+    }
+
     private IImsMmTelFeature createMmTelFeatureInternal(int slotId, int subscriptionId) {
         MmTelFeature f = createMmTelFeatureForSubscription(slotId, subscriptionId);
         if (f != null) {
             setupFeature(f, slotId, ImsFeature.FEATURE_MMTEL);
-            f.setDefaultExecutor(mExecutor);
+            f.setDefaultExecutor(getCachedExecutor());
             return f.getBinder();
         } else {
             Log.e(LOG_TAG, "createMmTelFeatureInternal: null feature returned.");
@@ -443,7 +450,7 @@
         MmTelFeature f = createEmergencyOnlyMmTelFeature(slotId);
         if (f != null) {
             setupFeature(f, slotId, ImsFeature.FEATURE_MMTEL);
-            f.setDefaultExecutor(mExecutor);
+            f.setDefaultExecutor(getCachedExecutor());
             return f.getBinder();
         } else {
             Log.e(LOG_TAG, "createEmergencyOnlyMmTelFeatureInternal: null feature returned.");
@@ -451,10 +458,10 @@
         }
     }
 
-    private IImsRcsFeature createRcsFeatureInternal(int slotId, int subI) {
-        RcsFeature f = createRcsFeatureForSubscription(slotId, subI);
+    private IImsRcsFeature createRcsFeatureInternal(int slotId, int subId) {
+        RcsFeature f = createRcsFeatureForSubscription(slotId, subId);
         if (f != null) {
-            f.setDefaultExecutor(mExecutor);
+            f.setDefaultExecutor(getCachedExecutor());
             setupFeature(f, slotId, ImsFeature.FEATURE_RCS);
             return f.getBinder();
         } else {
@@ -609,7 +616,8 @@
     private void executeMethodAsync(Runnable r, String errorLogName) {
         try {
             CompletableFuture.runAsync(
-                    () -> TelephonyUtils.runWithCleanCallingIdentity(r), mExecutor).join();
+                    () -> TelephonyUtils.runWithCleanCallingIdentity(r),
+                    getCachedExecutor()).join();
         } catch (CancellationException | CompletionException e) {
             Log.w(LOG_TAG, "ImsService Binder - " + errorLogName + " exception: "
                     + e.getMessage());
@@ -618,7 +626,7 @@
 
     private <T> T executeMethodAsyncForResult(Supplier<T> r, String errorLogName) {
         CompletableFuture<T> future = CompletableFuture.supplyAsync(
-                () -> TelephonyUtils.runWithCleanCallingIdentity(r), mExecutor);
+                () -> TelephonyUtils.runWithCleanCallingIdentity(r), getCachedExecutor());
         try {
             return future.get();
         } catch (ExecutionException | InterruptedException e) {
diff --git a/telephony/java/android/telephony/ims/feature/ImsFeature.java b/telephony/java/android/telephony/ims/feature/ImsFeature.java
index f5b158f..a42327b 100644
--- a/telephony/java/android/telephony/ims/feature/ImsFeature.java
+++ b/telephony/java/android/telephony/ims/feature/ImsFeature.java
@@ -394,10 +394,12 @@
     @VisibleForTesting
     public void addImsFeatureStatusCallback(@NonNull IImsFeatureStatusCallback c) {
         try {
-            // If we have just connected, send queued status.
-            c.notifyImsFeatureStatus(getFeatureState());
-            // Add the callback if the callback completes successfully without a RemoteException.
-            mStatusCallbacks.register(c);
+            synchronized (mStatusCallbacks) {
+                // Add the callback if the callback completes successfully without a RemoteException
+                mStatusCallbacks.register(c);
+                // If we have just connected, send queued status.
+                c.notifyImsFeatureStatus(getFeatureState());
+            }
         } catch (RemoteException e) {
             Log.w(LOG_TAG, "Couldn't notify feature state: " + e.getMessage());
         }
@@ -409,7 +411,9 @@
      */
     @VisibleForTesting
     public void removeImsFeatureStatusCallback(@NonNull IImsFeatureStatusCallback c) {
-        mStatusCallbacks.unregister(c);
+        synchronized (mStatusCallbacks) {
+            mStatusCallbacks.unregister(c);
+        }
     }
 
     /**
diff --git a/telephony/java/com/android/internal/telephony/IDomainSelectionServiceController.aidl b/telephony/java/com/android/internal/telephony/IDomainSelectionServiceController.aidl
new file mode 100644
index 0000000..8ad79ed
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/IDomainSelectionServiceController.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony;
+
+import android.telephony.BarringInfo;
+import android.telephony.DomainSelectionService.SelectionAttributes;
+import android.telephony.ServiceState;
+
+import com.android.internal.telephony.ITransportSelectorCallback;
+
+oneway interface IDomainSelectionServiceController {
+    void selectDomain(in SelectionAttributes attr, in ITransportSelectorCallback callback);
+    void updateServiceState(int slotId, int subId, in ServiceState serviceState);
+    void updateBarringInfo(int slotId, int subId, in BarringInfo info);
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl b/telephony/java/com/android/internal/telephony/IDomainSelector.aidl
similarity index 64%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
copy to telephony/java/com/android/internal/telephony/IDomainSelector.aidl
index 1550ab3..d94840b 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
+++ b/telephony/java/com/android/internal/telephony/IDomainSelector.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,6 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.systemui.shared.system;
+package com.android.internal.telephony;
 
-parcelable RemoteTransitionCompat;
+import android.telephony.DomainSelectionService.SelectionAttributes;
+
+oneway interface IDomainSelector {
+    void cancelSelection();
+    void reselectDomain(in SelectionAttributes attr);
+    void finishSelection();
+}
diff --git a/telephony/java/com/android/internal/telephony/ITransportSelectorCallback.aidl b/telephony/java/com/android/internal/telephony/ITransportSelectorCallback.aidl
new file mode 100644
index 0000000..aca83f4
--- /dev/null
+++ b/telephony/java/com/android/internal/telephony/ITransportSelectorCallback.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.telephony;
+
+import com.android.internal.telephony.IDomainSelector;
+import com.android.internal.telephony.ITransportSelectorResultCallback;
+import com.android.internal.telephony.IWwanSelectorCallback;
+
+interface ITransportSelectorCallback {
+    oneway void onCreated(in IDomainSelector selector);
+    oneway void onWlanSelected();
+    IWwanSelectorCallback onWwanSelected();
+    oneway void onWwanSelectedAsync(in ITransportSelectorResultCallback cb);
+    oneway void onSelectionTerminated(int cause);
+}
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl b/telephony/java/com/android/internal/telephony/ITransportSelectorResultCallback.aidl
similarity index 67%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
copy to telephony/java/com/android/internal/telephony/ITransportSelectorResultCallback.aidl
index 1550ab3..1460b45 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
+++ b/telephony/java/com/android/internal/telephony/ITransportSelectorResultCallback.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,6 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.systemui.shared.system;
+package com.android.internal.telephony;
 
-parcelable RemoteTransitionCompat;
+import com.android.internal.telephony.IWwanSelectorCallback;
+
+oneway interface ITransportSelectorResultCallback {
+    void onCompleted(in IWwanSelectorCallback cb);
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotifPanelEventsModule.java b/telephony/java/com/android/internal/telephony/IWwanSelectorCallback.aidl
similarity index 60%
copy from packages/SystemUI/src/com/android/systemui/shade/NotifPanelEventsModule.java
copy to telephony/java/com/android/internal/telephony/IWwanSelectorCallback.aidl
index 6772384..65d994b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotifPanelEventsModule.java
+++ b/telephony/java/com/android/internal/telephony/IWwanSelectorCallback.aidl
@@ -14,17 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.systemui.shade;
+package com.android.internal.telephony;
 
-import com.android.systemui.dagger.SysUISingleton;
+import com.android.internal.telephony.IWwanSelectorResultCallback;
 
-import dagger.Binds;
-import dagger.Module;
-
-/** Provides a {@link NotifPanelEvents} in {@link SysUISingleton} scope. */
-@Module
-public abstract class NotifPanelEventsModule {
-    @Binds
-    abstract NotifPanelEvents bindPanelEvents(
-            NotificationPanelViewController.PanelEventsEmitter impl);
+oneway interface IWwanSelectorCallback {
+    void onRequestEmergencyNetworkScan(in int[] preferredNetworks,
+            int scanType, in IWwanSelectorResultCallback cb);
+    void onDomainSelected(int accessNetworkType);
+    void onCancel();
 }
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl b/telephony/java/com/android/internal/telephony/IWwanSelectorResultCallback.aidl
similarity index 69%
copy from packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
copy to telephony/java/com/android/internal/telephony/IWwanSelectorResultCallback.aidl
index 1550ab3..0d61fcb 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/RemoteTransitionCompat.aidl
+++ b/telephony/java/com/android/internal/telephony/IWwanSelectorResultCallback.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2022 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,6 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.systemui.shared.system;
+package com.android.internal.telephony;
 
-parcelable RemoteTransitionCompat;
+import android.telephony.EmergencyRegResult;
+
+oneway interface IWwanSelectorResultCallback {
+    void onComplete(in EmergencyRegResult result);
+}
diff --git a/telephony/java/com/android/internal/telephony/PhoneConstants.java b/telephony/java/com/android/internal/telephony/PhoneConstants.java
index b2f7be6..0f83a05 100644
--- a/telephony/java/com/android/internal/telephony/PhoneConstants.java
+++ b/telephony/java/com/android/internal/telephony/PhoneConstants.java
@@ -239,4 +239,10 @@
     public static final int CELL_OFF_FLAG = 0;
     public static final int CELL_ON_FLAG = 1;
     public static final int CELL_OFF_DUE_TO_AIRPLANE_MODE_FLAG = 2;
+
+    /** The key to specify the selected domain for dialing calls. */
+    public static final String EXTRA_DIAL_DOMAIN = "dial_domain";
+
+    /** The key to specify the emergency service category */
+    public static final String EXTRA_EMERGENCY_SERVICE_CATEGORY = "emergency_service_category";
 }
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index 9892671..9f612e6 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -536,6 +536,12 @@
     int RIL_REQUEST_TRIGGER_EMERGENCY_NETWORK_SCAN = 230;
     int RIL_REQUEST_CANCEL_EMERGENCY_NETWORK_SCAN = 231;
     int RIL_REQUEST_EXIT_EMERGENCY_MODE = 232;
+    int RIL_REQUEST_SET_SRVCC_CALL_INFO = 233;
+    int RIL_REQUEST_UPDATE_IMS_REGISTRATION_INFO = 234;
+    int RIL_REQUEST_START_IMS_TRAFFIC = 235;
+    int RIL_REQUEST_STOP_IMS_TRAFFIC = 236;
+    int RIL_REQUEST_SEND_ANBR_QUERY = 237;
+    int RIL_REQUEST_TRIGGER_EPS_FALLBACK = 238;
 
     /* Responses begin */
     int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;
@@ -607,4 +613,7 @@
     int RIL_UNSOL_REGISTRATION_FAILED = 1104;
     int RIL_UNSOL_BARRING_INFO_CHANGED = 1105;
     int RIL_UNSOL_EMERGENCY_NETWORK_SCAN_RESULT = 1106;
+    int RIL_UNSOL_TRIGGER_IMS_DEREGISTRATION = 1107;
+    int RIL_UNSOL_CONNECTION_SETUP_FAILURE = 1108;
+    int RIL_UNSOL_NOTIFY_ANBR = 1109;
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt
index 16753e6..ca5b2af 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeAppAutoFocusHelper.kt
@@ -24,6 +24,8 @@
 import androidx.test.uiautomator.Until
 import com.android.server.wm.flicker.testapp.ActivityOptions
 import com.android.server.wm.traces.common.ComponentNameMatcher
+import com.android.server.wm.traces.common.Condition
+import com.android.server.wm.traces.common.DeviceStateDump
 import com.android.server.wm.traces.parser.toFlickerComponent
 import com.android.server.wm.traces.parser.windowmanager.WindowManagerStateHelper
 import java.util.regex.Pattern
@@ -47,9 +49,10 @@
         wmHelper: WindowManagerStateHelper,
         expectedWindowName: String,
         action: String?,
-        stringExtras: Map<String, String>
+        stringExtras: Map<String, String>,
+        waitConditions: Array<Condition<DeviceStateDump>>
     ) {
-        super.launchViaIntent(wmHelper, expectedWindowName, action, stringExtras)
+        super.launchViaIntent(wmHelper, expectedWindowName, action, stringExtras, waitConditions)
         waitIMEShown(wmHelper)
     }
 
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest_ShellTransit.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest_ShellTransit.kt
index 0837c00..5686965 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest_ShellTransit.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppAfterCameraTest_ShellTransit.kt
@@ -44,7 +44,7 @@
     OpenAppAfterCameraTest(testSpec) {
     @Before
     override fun before() {
-        Assume.assumeFalse(isShellTransitionsEnabled)
+        Assume.assumeTrue(isShellTransitionsEnabled)
     }
 
     @FlakyTest
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt
index 73e6d22..5e6fc21 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppColdFromIcon.kt
@@ -19,11 +19,11 @@
 import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.RequiresDevice
 import android.view.Surface
+import android.view.WindowManagerPolicyConstants
 import com.android.server.wm.flicker.FlickerParametersRunnerFactory
 import com.android.server.wm.flicker.FlickerTestParameter
 import com.android.server.wm.flicker.FlickerTestParameterFactory
 import com.android.server.wm.flicker.dsl.FlickerBuilder
-import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.rules.RemoveAllTasksButHomeRule
 import org.junit.FixMethodOrder
 import org.junit.Test
@@ -68,7 +68,6 @@
                     tapl.setExpectedRotation(Surface.ROTATION_0)
                 }
                 RemoveAllTasksButHomeRule.removeAllTasksButHome()
-                this.setRotation(testSpec.startRotation)
             }
             transitions {
                 tapl
@@ -187,7 +186,13 @@
         @Parameterized.Parameters(name = "{0}")
         @JvmStatic
         fun getParams(): Collection<FlickerTestParameter> {
-            return FlickerTestParameterFactory.getInstance().getConfigNonRotationTests()
+            return FlickerTestParameterFactory.getInstance()
+                // TAPL fails on landscape mode b/240916028
+                .getConfigNonRotationTests(
+                    supportedNavigationModes = listOf(
+                        WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON_OVERLAY
+                    )
+                )
         }
     }
 }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt
index 09d7637..0edbc86 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationCold.kt
@@ -97,8 +97,8 @@
         super.statusBarLayerPositionAtEnd()
 
     /** {@inheritDoc} */
-    @Postsubmit
     @Test
+    @Ignore("Not applicable to this CUJ. Display starts locked and app is full screen at the end")
     override fun navBarLayerIsVisibleAtStartAndEnd() = super.navBarLayerIsVisibleAtStartAndEnd()
 
     /** {@inheritDoc} */
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt
index c10b993..4ee1283 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/launch/OpenAppFromLockNotificationWithLockOverlayApp.kt
@@ -16,6 +16,7 @@
 
 package com.android.server.wm.flicker.launch
 
+import android.platform.test.annotations.FlakyTest
 import android.platform.test.annotations.Postsubmit
 import android.platform.test.annotations.Presubmit
 import android.platform.test.annotations.RequiresDevice
@@ -71,7 +72,7 @@
         }
 
     @Test
-    @Postsubmit
+    @FlakyTest(bugId = 227143265)
     fun showWhenLockedAppWindowBecomesVisible() {
         testSpec.assertWm {
             this.hasNoVisibleAppWindow()
@@ -83,7 +84,7 @@
     }
 
     @Test
-    @Postsubmit
+    @FlakyTest(bugId = 227143265)
     fun showWhenLockedAppLayerBecomesVisible() {
         testSpec.assertLayers {
             this.isInvisible(showWhenLockedApp)
@@ -98,11 +99,17 @@
     @Presubmit @Test override fun appLayerBecomesVisible() = super.appLayerBecomesVisible()
 
     /** {@inheritDoc} */
-    @Postsubmit
+    @FlakyTest(bugId = 227143265)
     @Test
     override fun visibleLayersShownMoreThanOneConsecutiveEntry() =
         super.visibleLayersShownMoreThanOneConsecutiveEntry()
 
+    /** {@inheritDoc} */
+    @FlakyTest(bugId = 209599395)
+    @Test
+    override fun navBarLayerIsVisibleAtStartAndEnd() =
+        super.navBarLayerIsVisibleAtStartAndEnd()
+
     companion object {
         /**
          * Creates the test configurations.
diff --git a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
index 6e935d1..83823ea 100644
--- a/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
+++ b/tests/FlickerTests/test-apps/flickerapp/AndroidManifest.xml
@@ -254,6 +254,8 @@
                 <action android:name="android.intent.action.MAIN"/>
                 <category android:name="android.intent.category.LAUNCHER"/>
             </intent-filter>
+            <meta-data android:name="android.app.shortcuts"
+                       android:resource="@xml/shortcuts" />
         </activity>
         <activity android:name=".SendNotificationActivity"
                   android:taskAffinity="com.android.server.wm.flicker.testapp.SendNotificationActivity"
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/values/strings.xml b/tests/FlickerTests/test-apps/flickerapp/res/values/strings.xml
new file mode 100644
index 0000000..24830de
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/values/strings.xml
@@ -0,0 +1,19 @@
+<!--
+  ~ Copyright (C) 2022 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Label for split screen shortcut-->
+    <string name="split_screen_shortcut_label">Split Screen Secondary Activity</string>
+</resources>
\ No newline at end of file
diff --git a/tests/FlickerTests/test-apps/flickerapp/res/xml/shortcuts.xml b/tests/FlickerTests/test-apps/flickerapp/res/xml/shortcuts.xml
new file mode 100644
index 0000000..804ec99
--- /dev/null
+++ b/tests/FlickerTests/test-apps/flickerapp/res/xml/shortcuts.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2022 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.
+  -->
+<shortcuts xmlns:android="http://schemas.android.com/apk/res/android">
+    <shortcut
+        android:shortcutId="split_screen_shortcut"
+        android:shortcutShortLabel="@string/split_screen_shortcut_label">
+        <intent
+            android:action="android.intent.action.VIEW"
+            android:targetPackage="com.android.server.wm.flicker.testapp"
+            android:targetClass="com.android.server.wm.flicker.testapp.SplitScreenSecondaryActivity" />
+    </shortcut>
+</shortcuts>
\ No newline at end of file
diff --git a/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java b/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java
index 29d87a2..ec1709c 100644
--- a/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java
+++ b/tests/RollbackTest/MultiUserRollbackTest/src/com/android/tests/rollback/host/MultiUserRollbackTest.java
@@ -25,6 +25,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -98,6 +99,7 @@
     }
 
     @Test
+    @Ignore
     public void testBadUpdateRollback() throws Exception {
         // Need to switch user in order to send broadcasts in device tests
         assertTrue(getDevice().switchUser(mSecondaryUserId));
diff --git a/tests/RollbackTest/SampleRollbackApp/Android.bp b/tests/RollbackTest/SampleRollbackApp/Android.bp
index a18488d..074c7bc 100644
--- a/tests/RollbackTest/SampleRollbackApp/Android.bp
+++ b/tests/RollbackTest/SampleRollbackApp/Android.bp
@@ -29,4 +29,5 @@
     resource_dirs: ["res"],
     certificate: "platform",
     sdk_version: "system_current",
+    min_sdk_version: "29",
 }